@fluidframework/fluid-runner 2.0.0-internal.1.2.1 → 2.0.0-internal.1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +27 -2
  2. package/dist/exportFile.d.ts +2 -1
  3. package/dist/exportFile.d.ts.map +1 -1
  4. package/dist/exportFile.js +5 -7
  5. package/dist/exportFile.js.map +1 -1
  6. package/dist/fakeUrlResolver.d.ts +1 -0
  7. package/dist/fakeUrlResolver.d.ts.map +1 -1
  8. package/dist/fakeUrlResolver.js +1 -0
  9. package/dist/fakeUrlResolver.js.map +1 -1
  10. package/dist/fluidRunner.d.ts.map +1 -1
  11. package/dist/fluidRunner.js +23 -13
  12. package/dist/fluidRunner.js.map +1 -1
  13. package/dist/getArgsValidationError.d.ts.map +1 -1
  14. package/dist/getArgsValidationError.js +0 -1
  15. package/dist/getArgsValidationError.js.map +1 -1
  16. package/dist/index.d.ts +3 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +9 -3
  19. package/dist/index.js.map +1 -1
  20. package/dist/logger/baseFileLogger.d.ts +28 -0
  21. package/dist/logger/baseFileLogger.d.ts.map +1 -0
  22. package/dist/logger/{FileLogger.js → baseFileLogger.js} +26 -34
  23. package/dist/logger/baseFileLogger.js.map +1 -0
  24. package/dist/logger/csvFileLogger.d.ts +18 -0
  25. package/dist/logger/csvFileLogger.d.ts.map +1 -0
  26. package/dist/logger/csvFileLogger.js +60 -0
  27. package/dist/logger/csvFileLogger.js.map +1 -0
  28. package/dist/logger/fileLogger.d.ts +38 -0
  29. package/dist/logger/fileLogger.d.ts.map +1 -0
  30. package/dist/logger/fileLogger.js +17 -0
  31. package/dist/logger/fileLogger.js.map +1 -0
  32. package/dist/logger/jsonFileLogger.d.ts +14 -0
  33. package/dist/logger/jsonFileLogger.d.ts.map +1 -0
  34. package/dist/logger/jsonFileLogger.js +44 -0
  35. package/dist/logger/jsonFileLogger.js.map +1 -0
  36. package/dist/logger/loggerUtils.d.ts +36 -0
  37. package/dist/logger/loggerUtils.d.ts.map +1 -0
  38. package/dist/logger/loggerUtils.js +90 -0
  39. package/dist/logger/loggerUtils.js.map +1 -0
  40. package/dist/parseBundleAndExportFile.d.ts +2 -1
  41. package/dist/parseBundleAndExportFile.d.ts.map +1 -1
  42. package/dist/parseBundleAndExportFile.js +6 -7
  43. package/dist/parseBundleAndExportFile.js.map +1 -1
  44. package/dist/utils.d.ts +7 -0
  45. package/dist/utils.d.ts.map +1 -1
  46. package/dist/utils.js +17 -1
  47. package/dist/utils.js.map +1 -1
  48. package/lib/exportFile.d.ts +2 -1
  49. package/lib/exportFile.d.ts.map +1 -1
  50. package/lib/exportFile.js +4 -6
  51. package/lib/exportFile.js.map +1 -1
  52. package/lib/fakeUrlResolver.d.ts +1 -0
  53. package/lib/fakeUrlResolver.d.ts.map +1 -1
  54. package/lib/fakeUrlResolver.js +1 -0
  55. package/lib/fakeUrlResolver.js.map +1 -1
  56. package/lib/fluidRunner.d.ts.map +1 -1
  57. package/lib/fluidRunner.js +23 -13
  58. package/lib/fluidRunner.js.map +1 -1
  59. package/lib/getArgsValidationError.d.ts.map +1 -1
  60. package/lib/getArgsValidationError.js +0 -1
  61. package/lib/getArgsValidationError.js.map +1 -1
  62. package/lib/index.d.ts +3 -1
  63. package/lib/index.d.ts.map +1 -1
  64. package/lib/index.js +4 -2
  65. package/lib/index.js.map +1 -1
  66. package/lib/logger/baseFileLogger.d.ts +28 -0
  67. package/lib/logger/baseFileLogger.d.ts.map +1 -0
  68. package/lib/logger/baseFileLogger.js +49 -0
  69. package/lib/logger/baseFileLogger.js.map +1 -0
  70. package/lib/logger/csvFileLogger.d.ts +18 -0
  71. package/lib/logger/csvFileLogger.d.ts.map +1 -0
  72. package/lib/logger/csvFileLogger.js +37 -0
  73. package/lib/logger/csvFileLogger.js.map +1 -0
  74. package/lib/logger/fileLogger.d.ts +38 -0
  75. package/lib/logger/fileLogger.d.ts.map +1 -0
  76. package/lib/logger/fileLogger.js +14 -0
  77. package/lib/logger/fileLogger.js.map +1 -0
  78. package/lib/logger/jsonFileLogger.d.ts +14 -0
  79. package/lib/logger/jsonFileLogger.d.ts.map +1 -0
  80. package/lib/logger/jsonFileLogger.js +21 -0
  81. package/lib/logger/jsonFileLogger.js.map +1 -0
  82. package/lib/logger/loggerUtils.d.ts +36 -0
  83. package/lib/logger/loggerUtils.d.ts.map +1 -0
  84. package/lib/logger/loggerUtils.js +65 -0
  85. package/lib/logger/loggerUtils.js.map +1 -0
  86. package/lib/parseBundleAndExportFile.d.ts +2 -1
  87. package/lib/parseBundleAndExportFile.d.ts.map +1 -1
  88. package/lib/parseBundleAndExportFile.js +5 -6
  89. package/lib/parseBundleAndExportFile.js.map +1 -1
  90. package/lib/utils.d.ts +7 -0
  91. package/lib/utils.d.ts.map +1 -1
  92. package/lib/utils.js +15 -0
  93. package/lib/utils.js.map +1 -1
  94. package/package.json +11 -10
  95. package/src/exportFile.ts +7 -5
  96. package/src/fakeUrlResolver.ts +1 -0
  97. package/src/fluidRunner.ts +24 -16
  98. package/src/getArgsValidationError.ts +0 -1
  99. package/src/index.ts +5 -2
  100. package/src/logger/baseFileLogger.ts +58 -0
  101. package/src/logger/csvFileLogger.ts +40 -0
  102. package/src/logger/fileLogger.ts +45 -0
  103. package/src/logger/jsonFileLogger.ts +27 -0
  104. package/src/logger/loggerUtils.ts +81 -0
  105. package/src/parseBundleAndExportFile.ts +7 -5
  106. package/src/utils.ts +20 -0
  107. package/dist/logger/FileLogger.d.ts +0 -32
  108. package/dist/logger/FileLogger.d.ts.map +0 -1
  109. package/dist/logger/FileLogger.js.map +0 -1
  110. package/lib/logger/FileLogger.d.ts +0 -32
  111. package/lib/logger/FileLogger.d.ts.map +0 -1
  112. package/lib/logger/FileLogger.js +0 -55
  113. package/lib/logger/FileLogger.js.map +0 -1
  114. package/src/logger/FileLogger.ts +0 -67
package/lib/utils.js CHANGED
@@ -6,6 +6,7 @@ import * as fs from "fs";
6
6
  /**
7
7
  * Is the given snapshot in JSON format
8
8
  * @param content - snapshot file content
9
+ * @internal
9
10
  */
10
11
  export function isJsonSnapshot(content) {
11
12
  return content.toString(undefined, 0, 1) === "{";
@@ -20,4 +21,18 @@ export function getSnapshotFileContent(filePath) {
20
21
  const content = fs.readFileSync(filePath);
21
22
  return isJsonSnapshot(content) ? content.toString() : content;
22
23
  }
24
+ /**
25
+ * Validate provided command line arguments
26
+ * @internal
27
+ */
28
+ export function validateCommandLineArgs(codeLoader, fluidFileConverter) {
29
+ if (codeLoader && fluidFileConverter !== undefined) {
30
+ return "\"codeLoader\" and \"fluidFileConverter\" cannot both be provided. See README for details.";
31
+ }
32
+ if (!codeLoader && fluidFileConverter === undefined) {
33
+ // eslint-disable-next-line max-len
34
+ return "\"codeLoader\" must be provided if there is no explicit \"fluidFileConverter\". See README for details.";
35
+ }
36
+ return undefined;
37
+ }
23
38
  //# sourceMappingURL=utils.js.map
package/lib/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AAEzB;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC1C,OAAO,OAAO,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC;AACrD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAgB;IACnD,yBAAyB;IACzB,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC1C,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;AAClE,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport * as fs from \"fs\";\n\n/**\n * Is the given snapshot in JSON format\n * @param content - snapshot file content\n */\nexport function isJsonSnapshot(content: Buffer): boolean {\n return content.toString(undefined, 0, 1) === \"{\";\n}\n\n/**\n * Get the ODSP snapshot file content\n * Works on both JSON and binary snapshot formats\n * @param filePath - path to the ODSP snapshot file\n */\nexport function getSnapshotFileContent(filePath: string): string | Buffer {\n // TODO: read file stream\n const content = fs.readFileSync(filePath);\n return isJsonSnapshot(content) ? content.toString() : content;\n}\n"]}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AAGzB;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC1C,OAAO,OAAO,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC;AACrD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAgB;IACnD,yBAAyB;IACzB,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC1C,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;AAClE,CAAC;AAED;;;GAGG;AACF,MAAM,UAAU,uBAAuB,CACpC,UAAmB,EACnB,kBAAwC;IAExC,IAAI,UAAU,IAAI,kBAAkB,KAAK,SAAS,EAAE;QAChD,OAAO,4FAA4F,CAAC;KACvG;IACD,IAAI,CAAC,UAAU,IAAI,kBAAkB,KAAK,SAAS,EAAE;QACjD,mCAAmC;QACnC,OAAO,yGAAyG,CAAC;KACpH;IACD,OAAO,SAAS,CAAC;AACrB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport * as fs from \"fs\";\nimport { IFluidFileConverter } from \"./codeLoaderBundle\";\n\n/**\n * Is the given snapshot in JSON format\n * @param content - snapshot file content\n * @internal\n */\nexport function isJsonSnapshot(content: Buffer): boolean {\n return content.toString(undefined, 0, 1) === \"{\";\n}\n\n/**\n * Get the ODSP snapshot file content\n * Works on both JSON and binary snapshot formats\n * @param filePath - path to the ODSP snapshot file\n */\nexport function getSnapshotFileContent(filePath: string): string | Buffer {\n // TODO: read file stream\n const content = fs.readFileSync(filePath);\n return isJsonSnapshot(content) ? content.toString() : content;\n}\n\n/**\n * Validate provided command line arguments\n * @internal\n */\n export function validateCommandLineArgs(\n codeLoader?: string,\n fluidFileConverter?: IFluidFileConverter,\n): string | undefined {\n if (codeLoader && fluidFileConverter !== undefined) {\n return \"\\\"codeLoader\\\" and \\\"fluidFileConverter\\\" cannot both be provided. See README for details.\";\n }\n if (!codeLoader && fluidFileConverter === undefined) {\n // eslint-disable-next-line max-len\n return \"\\\"codeLoader\\\" must be provided if there is no explicit \\\"fluidFileConverter\\\". See README for details.\";\n }\n return undefined;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/fluid-runner",
3
- "version": "2.0.0-internal.1.2.1",
3
+ "version": "2.0.0-internal.1.2.2",
4
4
  "description": "Utility for running various functionality inside a Fluid Framework environment",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -57,21 +57,22 @@
57
57
  "temp-directory": "nyc/.nyc_output"
58
58
  },
59
59
  "dependencies": {
60
- "@fluidframework/aqueduct": "^2.0.0-internal.1.2.1",
60
+ "@fluidframework/aqueduct": "^2.0.0-internal.1.2.2",
61
61
  "@fluidframework/common-definitions": "^0.20.1",
62
- "@fluidframework/container-definitions": "^2.0.0-internal.1.2.1",
63
- "@fluidframework/container-loader": "^2.0.0-internal.1.2.1",
64
- "@fluidframework/core-interfaces": "^2.0.0-internal.1.2.1",
65
- "@fluidframework/driver-definitions": "^2.0.0-internal.1.2.1",
66
- "@fluidframework/odsp-driver": "^2.0.0-internal.1.2.1",
67
- "@fluidframework/odsp-driver-definitions": "^2.0.0-internal.1.2.1",
68
- "@fluidframework/telemetry-utils": "^2.0.0-internal.1.2.1",
62
+ "@fluidframework/container-definitions": "^2.0.0-internal.1.2.2",
63
+ "@fluidframework/container-loader": "^2.0.0-internal.1.2.2",
64
+ "@fluidframework/core-interfaces": "^2.0.0-internal.1.2.2",
65
+ "@fluidframework/driver-definitions": "^2.0.0-internal.1.2.2",
66
+ "@fluidframework/odsp-driver": "^2.0.0-internal.1.2.2",
67
+ "@fluidframework/odsp-driver-definitions": "^2.0.0-internal.1.2.2",
68
+ "@fluidframework/telemetry-utils": "^2.0.0-internal.1.2.2",
69
+ "json2csv": "^5.0.7",
69
70
  "yargs": "13.2.2"
70
71
  },
71
72
  "devDependencies": {
72
73
  "@fluidframework/build-common": "^1.0.0",
73
74
  "@fluidframework/eslint-config-fluid": "^1.0.0",
74
- "@fluidframework/mocha-test-setup": "^2.0.0-internal.1.2.1",
75
+ "@fluidframework/mocha-test-setup": "^2.0.0-internal.1.2.2",
75
76
  "@rushstack/eslint-config": "^2.5.1",
76
77
  "@types/mocha": "^9.1.1",
77
78
  "@types/node": "^14.18.0",
package/src/exportFile.ts CHANGED
@@ -13,8 +13,10 @@ import { getArgsValidationError } from "./getArgsValidationError";
13
13
  import { IFluidFileConverter } from "./codeLoaderBundle";
14
14
  import { FakeUrlResolver } from "./fakeUrlResolver";
15
15
  import { getSnapshotFileContent } from "./utils";
16
- // eslint-disable-next-line import/no-internal-modules
17
- import { createLogger, FileLogger, getTelemetryFileValidationError } from "./logger/FileLogger";
16
+ /* eslint-disable import/no-internal-modules */
17
+ import { ITelemetryOptions } from "./logger/fileLogger";
18
+ import { createLogger, getTelemetryFileValidationError } from "./logger/loggerUtils";
19
+ /* eslint-enable import/no-internal-modules */
18
20
 
19
21
  export type IExportFileResponse = IExportFileResponseSuccess | IExportFileResponseFailure;
20
22
 
@@ -40,14 +42,14 @@ export async function exportFile(
40
42
  outputFile: string,
41
43
  telemetryFile: string,
42
44
  options?: string,
45
+ telemetryOptions?: ITelemetryOptions,
43
46
  ): Promise<IExportFileResponse> {
44
47
  const telemetryArgError = getTelemetryFileValidationError(telemetryFile);
45
48
  if (telemetryArgError) {
46
49
  const eventName = clientArgsValidationError;
47
50
  return { success: false, eventName, errorMessage: telemetryArgError };
48
51
  }
49
- const fileLogger = new FileLogger(telemetryFile);
50
- const logger = createLogger(fileLogger);
52
+ const { fileLogger, logger } = createLogger(telemetryFile, telemetryOptions);
51
53
 
52
54
  try {
53
55
  return await PerformanceEvent.timedExecAsync(logger, { eventName: "ExportFile" }, async () => {
@@ -72,7 +74,7 @@ export async function exportFile(
72
74
  logger.sendErrorEvent({ eventName }, error);
73
75
  return { success: false, eventName, errorMessage: "Unexpected error", error };
74
76
  } finally {
75
- await fileLogger.flush();
77
+ await fileLogger.close();
76
78
  }
77
79
  }
78
80
 
@@ -12,6 +12,7 @@ const fakeUrl = "/FakeUrlResolver/";
12
12
 
13
13
  /**
14
14
  * Fake URL resolver that returns hard coded values on every request
15
+ * @internal
15
16
  */
16
17
  export class FakeUrlResolver implements IUrlResolver {
17
18
  public async resolve(_request: IRequest): Promise<IResolvedUrl | undefined> {
@@ -3,10 +3,14 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ /* eslint-disable max-len */
6
7
  import * as yargs from "yargs";
7
8
  import { exportFile } from "./exportFile";
8
9
  import { IFluidFileConverter } from "./codeLoaderBundle";
9
10
  import { parseBundleAndExportFile } from "./parseBundleAndExportFile";
11
+ // eslint-disable-next-line import/no-internal-modules
12
+ import { validateAndParseTelemetryOptions } from "./logger/loggerUtils";
13
+ import { validateCommandLineArgs } from "./utils";
10
14
 
11
15
  /**
12
16
  * @param fluidFileConverter - needs to be provided if "codeLoaderBundle" is not and vice versa
@@ -23,7 +27,6 @@ export function fluidRunner(fluidFileConverter?: IFluidFileConverter) {
23
27
  (yargs) =>
24
28
  yargs
25
29
  .option("codeLoader", {
26
- // eslint-disable-next-line max-len
27
30
  describe: "Path to code loader bundle. Required if this application is being called without modification.\nSee \"README.md\" for more details.",
28
31
  type: "string",
29
32
  demandOption: false,
@@ -47,14 +50,30 @@ export function fluidRunner(fluidFileConverter?: IFluidFileConverter) {
47
50
  describe: "Additional options passed to container on execution",
48
51
  type: "string",
49
52
  demandOption: false,
53
+ })
54
+ .option("telemetryFormat", {
55
+ describe: "Output format for telemetry. Current options are: [\"JSON\", \"CSV\"]",
56
+ type: "string",
57
+ demandOption: false,
58
+ default: "JSON",
59
+ })
60
+ .option("telemetryProp", {
61
+ describe: "Property to add to every telemetry entry. Formatted like \"--telemetryProp prop1 value1 --telemetryProp prop2 \\\"value 2\\\"\".",
62
+ type: "array",
63
+ demandOption: false,
50
64
  }),
51
65
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
52
66
  async (argv) => {
53
- const argsError = validateProvidedArgs(argv.codeLoader, fluidFileConverter);
67
+ const argsError = validateCommandLineArgs(argv.codeLoader, fluidFileConverter);
54
68
  if (argsError) {
55
69
  console.error(argsError);
56
70
  process.exit(1);
57
71
  }
72
+ const telemetryOptionsResult = validateAndParseTelemetryOptions(argv.telemetryFormat, argv.telemetryProp);
73
+ if (!telemetryOptionsResult.success) {
74
+ console.error(telemetryOptionsResult.error);
75
+ process.exit(1);
76
+ }
58
77
 
59
78
  const result = await (argv.codeLoader
60
79
  ? parseBundleAndExportFile(
@@ -63,12 +82,14 @@ export function fluidRunner(fluidFileConverter?: IFluidFileConverter) {
63
82
  argv.outputFile,
64
83
  argv.telemetryFile,
65
84
  argv.options,
85
+ telemetryOptionsResult.telemetryOptions,
66
86
  ) : exportFile(
67
87
  fluidFileConverter!,
68
88
  argv.inputFile,
69
89
  argv.outputFile,
70
90
  argv.telemetryFile,
71
91
  argv.options,
92
+ telemetryOptionsResult.telemetryOptions,
72
93
  ));
73
94
 
74
95
  if (!result.success) {
@@ -81,18 +102,5 @@ export function fluidRunner(fluidFileConverter?: IFluidFileConverter) {
81
102
  .demandCommand().argv;
82
103
  }
83
104
 
84
- function validateProvidedArgs(
85
- codeLoader?: string,
86
- fluidFileConverter?: IFluidFileConverter,
87
- ): string | undefined {
88
- if (codeLoader !== undefined && fluidFileConverter !== undefined) {
89
- return "\"codeLoader\" and \"fluidFileConverter\" cannot both be provided. See \"fluidRunner.ts\" for details.";
90
- }
91
- if (codeLoader === undefined && fluidFileConverter === undefined) {
92
- // eslint-disable-next-line max-len
93
- return "\"codeLoader\" must be provided if there is no explicit \"fluidFileConverter\". See \"fluidRunner.ts\" for details.";
94
- }
95
- return undefined;
96
- }
97
-
98
105
  fluidRunner();
106
+ /* eslint-enable max-len */
@@ -11,7 +11,6 @@ export function getArgsValidationError(
11
11
  ): string | undefined {
12
12
  // Validate input file
13
13
  if (!inputFile) {
14
- // TODO: Do not log file name. It can be customer content
15
14
  return "Input file name argument is missing.";
16
15
  } else if (!fs.existsSync(inputFile)) {
17
16
  return "Input file does not exist.";
package/src/index.ts CHANGED
@@ -3,9 +3,12 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ /* eslint-disable import/no-internal-modules */
6
7
  export { ICodeLoaderBundle, IFluidFileConverter } from "./codeLoaderBundle";
7
8
  export * from "./exportFile";
8
9
  export { fluidRunner } from "./fluidRunner";
9
- // eslint-disable-next-line import/no-internal-modules
10
- export * from "./logger/FileLogger";
10
+ export { OutputFormat } from "./logger/fileLogger";
11
+ export { createLogger, getTelemetryFileValidationError } from "./logger/loggerUtils";
11
12
  export * from "./parseBundleAndExportFile";
13
+ export { getSnapshotFileContent } from "./utils";
14
+ /* eslint-enable import/no-internal-modules */
@@ -0,0 +1,58 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import * as fs from "fs";
7
+ import { ITelemetryBaseEvent } from "@fluidframework/common-definitions";
8
+ import { IFileLogger } from "./fileLogger";
9
+
10
+ /**
11
+ * @internal
12
+ */
13
+ export abstract class BaseFileLogger implements IFileLogger {
14
+ public supportsTags?: true | undefined;
15
+
16
+ /** Hold events in memory until flushed */
17
+ protected events: any[] = [];
18
+ protected hasWrittenToFile = false;
19
+
20
+ /**
21
+ * @param filePath - file path to write logs to
22
+ * @param eventsPerFlush - number of events per flush
23
+ * @param defaultProps - default properties to add to every telemetry event
24
+ */
25
+ public constructor(
26
+ protected readonly filePath: string,
27
+ protected readonly eventsPerFlush: number = 50,
28
+ protected readonly defaultProps?: Record<string, string | number>,
29
+ ) { }
30
+
31
+ public send(event: ITelemetryBaseEvent): void {
32
+ // eslint-disable-next-line no-param-reassign
33
+ event = { ...event, ...this.defaultProps };
34
+ this.events.push(event);
35
+
36
+ if (this.events.length >= this.eventsPerFlush || event.category === "error") {
37
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
38
+ this.flush();
39
+ }
40
+ }
41
+
42
+ protected async flush(): Promise<void> {
43
+ if (this.events.length > 0) {
44
+ const contentToWrite = this.events.map((it) => JSON.stringify(it)).join(",");
45
+ if (this.hasWrittenToFile) {
46
+ fs.appendFileSync(this.filePath, `,${contentToWrite}`);
47
+ } else {
48
+ fs.appendFileSync(this.filePath, contentToWrite);
49
+ }
50
+ this.events = [];
51
+ this.hasWrittenToFile = true;
52
+ }
53
+ }
54
+
55
+ public async close(): Promise<void> {
56
+ await this.flush();
57
+ }
58
+ }
@@ -0,0 +1,40 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import * as fs from "fs";
7
+ import { parse } from "json2csv";
8
+ import { ITelemetryBaseEvent } from "@fluidframework/common-definitions";
9
+ import { BaseFileLogger } from "./baseFileLogger";
10
+
11
+ /**
12
+ * FileLogger that writes events into a defined CSV file
13
+ * @internal
14
+ */
15
+ export class CSVFileLogger extends BaseFileLogger {
16
+ /** Store the column names to write as the CSV header */
17
+ private readonly columns = new Set();
18
+
19
+ public async flush(): Promise<void> {
20
+ // Do nothing
21
+ }
22
+
23
+ public send(event: ITelemetryBaseEvent): void {
24
+ // eslint-disable-next-line guard-for-in, no-restricted-syntax
25
+ for (const prop in event) {
26
+ this.columns.add(prop);
27
+ }
28
+ super.send(event);
29
+ }
30
+
31
+ public async close(): Promise<void> {
32
+ await super.close();
33
+ // eslint-disable-next-line guard-for-in, no-restricted-syntax
34
+ for (const field in this.defaultProps) {
35
+ this.columns.add(field);
36
+ }
37
+
38
+ fs.writeFileSync(this.filePath, parse(this.events, Array.from(this.columns)));
39
+ }
40
+ }
@@ -0,0 +1,45 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { ITelemetryBaseLogger } from "@fluidframework/common-definitions";
7
+
8
+ /**
9
+ * Contract for logger that writes telemetry to a file
10
+ * @internal
11
+ */
12
+ export interface IFileLogger extends ITelemetryBaseLogger {
13
+ /**
14
+ * This method acts as a "dispose" and should be explicitly called at the end of execution
15
+ */
16
+ close(): Promise<void>;
17
+ }
18
+
19
+ /**
20
+ * Desired output format for the telemetry
21
+ */
22
+ export enum OutputFormat {
23
+ JSON,
24
+ CSV,
25
+ }
26
+
27
+ /* eslint-disable tsdoc/syntax */
28
+ /**
29
+ * Options to provide upon creation of IFileLogger
30
+ * @internal
31
+ */
32
+ export interface ITelemetryOptions {
33
+ /** Desired output format used to create a specific IFileLogger implementation */
34
+ outputFormat?: OutputFormat;
35
+
36
+ /**
37
+ * Properties that should be added to every telemetry event
38
+ * Example: { "prop1": "value1", "prop2": 10.0 }
39
+ */
40
+ defaultProps?: Record<string, string | number>;
41
+
42
+ /** Number of telemetry events per flush to telemetry file */
43
+ eventsPerFlush?: number;
44
+ }
45
+ /* eslint-enable tsdoc/syntax */
@@ -0,0 +1,27 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import * as fs from "fs";
7
+ import { BaseFileLogger } from "./baseFileLogger";
8
+
9
+ /**
10
+ * FileLogger that writes events into a defined CSV file
11
+ * @internal
12
+ */
13
+ export class JSONFileLogger extends BaseFileLogger {
14
+ constructor(
15
+ filePath: string,
16
+ eventsPerFlush: number = 50,
17
+ defaultProps?: Record<string, string | number>,
18
+ ) {
19
+ super(filePath, eventsPerFlush, defaultProps);
20
+ fs.appendFileSync(this.filePath, "[");
21
+ }
22
+
23
+ public async close(): Promise<void> {
24
+ await super.close();
25
+ fs.appendFileSync(this.filePath, "]");
26
+ }
27
+ }
@@ -0,0 +1,81 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import * as fs from "fs";
7
+ import { ITelemetryLogger } from "@fluidframework/common-definitions";
8
+ import { ChildLogger } from "@fluidframework/telemetry-utils";
9
+ import { CSVFileLogger } from "./csvFileLogger";
10
+ import { IFileLogger, ITelemetryOptions, OutputFormat } from "./fileLogger";
11
+ import { JSONFileLogger } from "./jsonFileLogger";
12
+
13
+ /**
14
+ * Create a ITelemetryLogger wrapped around provided IFileLogger
15
+ * ! It is expected that all events be sent through the returned "logger" value
16
+ * ! The "fileLogger" value should have its "close()" method called at the end of execution
17
+ * Note: if an output format is not supplied, default is JSON
18
+ * @returns - both the IFileLogger implementation and ITelemetryLogger wrapper to be called
19
+ */
20
+ export function createLogger(
21
+ filePath: string,
22
+ options?: ITelemetryOptions,
23
+ ): { logger: ITelemetryLogger; fileLogger: IFileLogger; } {
24
+ const fileLogger = options?.outputFormat === OutputFormat.CSV
25
+ ? new CSVFileLogger(filePath, options?.eventsPerFlush, options?.defaultProps)
26
+ : new JSONFileLogger(filePath, options?.eventsPerFlush, options?.defaultProps);
27
+
28
+ const logger = ChildLogger.create(fileLogger, "LocalSnapshotRunnerApp",
29
+ { all: { Event_Time: () => Date.now() } });
30
+
31
+ return { logger, fileLogger };
32
+ }
33
+
34
+ /**
35
+ * Validate the telemetryFile command line argument
36
+ * @param telemetryFile - path where telemetry will be written
37
+ */
38
+ export function getTelemetryFileValidationError(telemetryFile: string): string | undefined {
39
+ if (!telemetryFile) {
40
+ return "Telemetry file argument is missing.";
41
+ } else if (fs.existsSync(telemetryFile)) {
42
+ return `Telemetry file already exists [${telemetryFile}].`;
43
+ }
44
+
45
+ return undefined;
46
+ }
47
+
48
+ /**
49
+ * Validate the provided output format and default properties
50
+ * @param format - desired output format of the telemetry
51
+ * @param props - default properties to be added to every telemetry entry
52
+ * @internal
53
+ */
54
+ export function validateAndParseTelemetryOptions(
55
+ format?: string,
56
+ props?: (string | number)[],
57
+ ): { success: false; error: string; } | { success: true; telemetryOptions: ITelemetryOptions; } {
58
+ let outputFormat: OutputFormat | undefined;
59
+ const defaultProps: Record<string, string | number> = {};
60
+
61
+ if (format) {
62
+ outputFormat = OutputFormat[format];
63
+ if (outputFormat === undefined) {
64
+ return { success: false, error: `Invalid telemetry format [${format}]` };
65
+ }
66
+ }
67
+
68
+ if (props && props.length > 0) {
69
+ if (props.length % 2 !== 0) {
70
+ return { success: false, error: `Invalid number of telemetry properties to add [${props.length}]` };
71
+ }
72
+ for (let i = 0; i < props.length; i += 2) {
73
+ if (typeof props[i] === "number") {
74
+ return { success: false, error: `Property name cannot be number at index [${i}] -> [${props[i]}]` };
75
+ }
76
+ defaultProps[props[i]] = props[i + 1];
77
+ }
78
+ }
79
+
80
+ return { success: true, telemetryOptions: { outputFormat, defaultProps } };
81
+ }
@@ -8,8 +8,10 @@ import { PerformanceEvent } from "@fluidframework/telemetry-utils";
8
8
  import { isCodeLoaderBundle, isFluidFileConverter } from "./codeLoaderBundle";
9
9
  import { createContainerAndExecute, IExportFileResponse } from "./exportFile";
10
10
  import { getArgsValidationError } from "./getArgsValidationError";
11
- // eslint-disable-next-line import/no-internal-modules
12
- import { createLogger, FileLogger, getTelemetryFileValidationError } from "./logger/FileLogger";
11
+ /* eslint-disable import/no-internal-modules */
12
+ import { ITelemetryOptions } from "./logger/fileLogger";
13
+ import { createLogger, getTelemetryFileValidationError } from "./logger/loggerUtils";
14
+ /* eslint-enable import/no-internal-modules */
13
15
  import { getSnapshotFileContent } from "./utils";
14
16
 
15
17
  const clientArgsValidationError = "Client_ArgsValidationError";
@@ -24,14 +26,14 @@ export async function parseBundleAndExportFile(
24
26
  outputFile: string,
25
27
  telemetryFile: string,
26
28
  options?: string,
29
+ telemetryOptions?: ITelemetryOptions,
27
30
  ): Promise<IExportFileResponse> {
28
31
  const telemetryArgError = getTelemetryFileValidationError(telemetryFile);
29
32
  if (telemetryArgError) {
30
33
  const eventName = clientArgsValidationError;
31
34
  return { success: false, eventName, errorMessage: telemetryArgError };
32
35
  }
33
- const fileLogger = new FileLogger(telemetryFile);
34
- const logger = createLogger(fileLogger);
36
+ const { fileLogger, logger } = createLogger(telemetryFile, telemetryOptions);
35
37
 
36
38
  try {
37
39
  return await PerformanceEvent.timedExecAsync(logger, { eventName: "ParseBundleAndExportFile" }, async () => {
@@ -73,6 +75,6 @@ export async function parseBundleAndExportFile(
73
75
  logger.sendErrorEvent({ eventName }, error);
74
76
  return { success: false, eventName, errorMessage: "Unexpected error", error };
75
77
  } finally {
76
- await fileLogger.flush();
78
+ await fileLogger.close();
77
79
  }
78
80
  }
package/src/utils.ts CHANGED
@@ -4,10 +4,12 @@
4
4
  */
5
5
 
6
6
  import * as fs from "fs";
7
+ import { IFluidFileConverter } from "./codeLoaderBundle";
7
8
 
8
9
  /**
9
10
  * Is the given snapshot in JSON format
10
11
  * @param content - snapshot file content
12
+ * @internal
11
13
  */
12
14
  export function isJsonSnapshot(content: Buffer): boolean {
13
15
  return content.toString(undefined, 0, 1) === "{";
@@ -23,3 +25,21 @@ export function getSnapshotFileContent(filePath: string): string | Buffer {
23
25
  const content = fs.readFileSync(filePath);
24
26
  return isJsonSnapshot(content) ? content.toString() : content;
25
27
  }
28
+
29
+ /**
30
+ * Validate provided command line arguments
31
+ * @internal
32
+ */
33
+ export function validateCommandLineArgs(
34
+ codeLoader?: string,
35
+ fluidFileConverter?: IFluidFileConverter,
36
+ ): string | undefined {
37
+ if (codeLoader && fluidFileConverter !== undefined) {
38
+ return "\"codeLoader\" and \"fluidFileConverter\" cannot both be provided. See README for details.";
39
+ }
40
+ if (!codeLoader && fluidFileConverter === undefined) {
41
+ // eslint-disable-next-line max-len
42
+ return "\"codeLoader\" must be provided if there is no explicit \"fluidFileConverter\". See README for details.";
43
+ }
44
+ return undefined;
45
+ }
@@ -1,32 +0,0 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
- import { ITelemetryBaseEvent, ITelemetryBaseLogger, ITelemetryLogger } from "@fluidframework/common-definitions";
6
- /**
7
- * Logger that writes events into a defined file
8
- */
9
- export declare class FileLogger implements ITelemetryBaseLogger {
10
- private readonly filePath;
11
- private readonly eventsPerFlush;
12
- supportsTags?: true | undefined;
13
- /** Hold events in memory until flushed */
14
- private events;
15
- /**
16
- * @param filePath - file path to write logs to
17
- * @param eventsPerFlush - number of events per flush
18
- */
19
- constructor(filePath: string, eventsPerFlush?: number);
20
- flush(): Promise<void>;
21
- send(event: ITelemetryBaseEvent): void;
22
- }
23
- /**
24
- * Create a ITelemetryLogger wrapped around provided FileLogger
25
- */
26
- export declare function createLogger(fileLogger: FileLogger): ITelemetryLogger;
27
- /**
28
- * Validate the telemetryFile command line argument
29
- * @param telemetryFile - path where telemetry will be written
30
- */
31
- export declare function getTelemetryFileValidationError(telemetryFile: string): string | undefined;
32
- //# sourceMappingURL=FileLogger.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"FileLogger.d.ts","sourceRoot":"","sources":["../../src/logger/FileLogger.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAGjH;;GAEG;AACH,qBAAa,UAAW,YAAW,oBAAoB;IAW/C,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAX5B,YAAY,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAEvC,0CAA0C;IAC1C,OAAO,CAAC,MAAM,CAAgB;IAE9B;;;OAGG;gBAEkB,QAAQ,EAAE,MAAM,EAChB,cAAc,GAAE,MAAW;IAGnC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B,IAAI,CAAC,KAAK,EAAE,mBAAmB,GAAG,IAAI;CAUhD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,UAAU,GAAG,gBAAgB,CAGrE;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQzF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"FileLogger.js","sourceRoot":"","sources":["../../src/logger/FileLogger.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;;;;;;;;;;;;;;;;;AAEH,uCAAyB;AAEzB,qEAA8D;AAE9D;;GAEG;AACH,MAAa,UAAU;IAMnB;;;OAGG;IACH,YACqB,QAAgB,EAChB,iBAAyB,EAAE;QAD3B,aAAQ,GAAR,QAAQ,CAAQ;QAChB,mBAAc,GAAd,cAAc,CAAa;QAThD,0CAA0C;QAClC,WAAM,GAAa,EAAE,CAAC;IAS1B,CAAC;IAEE,KAAK,CAAC,KAAK;QACd,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE;YACxB,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YACzD,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;SACpB;IACL,CAAC;IAEM,IAAI,CAAC,KAA0B;QAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAEvC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAE3B,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,cAAc,IAAI,KAAK,CAAC,QAAQ,KAAK,OAAO,EAAE;YACzE,mEAAmE;YACnE,IAAI,CAAC,KAAK,EAAE,CAAC;SAChB;IACL,CAAC;CACJ;AAhCD,gCAgCC;AAED;;GAEG;AACH,SAAgB,YAAY,CAAC,UAAsB;IAC/C,OAAO,6BAAW,CAAC,MAAM,CAAC,UAAU,EAAE,wBAAwB,EAC1D,EAAE,GAAG,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;AACnD,CAAC;AAHD,oCAGC;AAED;;;GAGG;AACH,SAAgB,+BAA+B,CAAC,aAAqB;IACjE,IAAI,CAAC,aAAa,EAAE;QAChB,OAAO,qCAAqC,CAAC;KAChD;SAAM,IAAI,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE;QACrC,OAAO,kCAAkC,aAAa,IAAI,CAAC;KAC9D;IAED,OAAO,SAAS,CAAC;AACrB,CAAC;AARD,0EAQC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport * as fs from \"fs\";\nimport { ITelemetryBaseEvent, ITelemetryBaseLogger, ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { ChildLogger } from \"@fluidframework/telemetry-utils\";\n\n/**\n * Logger that writes events into a defined file\n */\nexport class FileLogger implements ITelemetryBaseLogger {\n public supportsTags?: true | undefined;\n\n /** Hold events in memory until flushed */\n private events: string[] = [];\n\n /**\n * @param filePath - file path to write logs to\n * @param eventsPerFlush - number of events per flush\n */\n public constructor(\n private readonly filePath: string,\n private readonly eventsPerFlush: number = 50,\n ) { }\n\n public async flush(): Promise<void> {\n if (this.events.length > 0) {\n fs.appendFileSync(this.filePath, this.events.join(\"\\n\"));\n this.events = [];\n }\n }\n\n public send(event: ITelemetryBaseEvent): void {\n const logEvent = JSON.stringify(event);\n\n this.events.push(logEvent);\n\n if (this.events.length >= this.eventsPerFlush || event.category === \"error\") {\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.flush();\n }\n }\n}\n\n/**\n * Create a ITelemetryLogger wrapped around provided FileLogger\n */\nexport function createLogger(fileLogger: FileLogger): ITelemetryLogger {\n return ChildLogger.create(fileLogger, \"LocalSnapshotRunnerApp\",\n { all: { Event_Time: () => Date.now() } });\n}\n\n/**\n * Validate the telemetryFile command line argument\n * @param telemetryFile - path where telemetry will be written\n */\nexport function getTelemetryFileValidationError(telemetryFile: string): string | undefined {\n if (!telemetryFile) {\n return \"Telemetry file argument is missing.\";\n } else if (fs.existsSync(telemetryFile)) {\n return `Telemetry file already exists [${telemetryFile}].`;\n }\n\n return undefined;\n}\n"]}
@@ -1,32 +0,0 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
- import { ITelemetryBaseEvent, ITelemetryBaseLogger, ITelemetryLogger } from "@fluidframework/common-definitions";
6
- /**
7
- * Logger that writes events into a defined file
8
- */
9
- export declare class FileLogger implements ITelemetryBaseLogger {
10
- private readonly filePath;
11
- private readonly eventsPerFlush;
12
- supportsTags?: true | undefined;
13
- /** Hold events in memory until flushed */
14
- private events;
15
- /**
16
- * @param filePath - file path to write logs to
17
- * @param eventsPerFlush - number of events per flush
18
- */
19
- constructor(filePath: string, eventsPerFlush?: number);
20
- flush(): Promise<void>;
21
- send(event: ITelemetryBaseEvent): void;
22
- }
23
- /**
24
- * Create a ITelemetryLogger wrapped around provided FileLogger
25
- */
26
- export declare function createLogger(fileLogger: FileLogger): ITelemetryLogger;
27
- /**
28
- * Validate the telemetryFile command line argument
29
- * @param telemetryFile - path where telemetry will be written
30
- */
31
- export declare function getTelemetryFileValidationError(telemetryFile: string): string | undefined;
32
- //# sourceMappingURL=FileLogger.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"FileLogger.d.ts","sourceRoot":"","sources":["../../src/logger/FileLogger.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAGjH;;GAEG;AACH,qBAAa,UAAW,YAAW,oBAAoB;IAW/C,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAX5B,YAAY,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAEvC,0CAA0C;IAC1C,OAAO,CAAC,MAAM,CAAgB;IAE9B;;;OAGG;gBAEkB,QAAQ,EAAE,MAAM,EAChB,cAAc,GAAE,MAAW;IAGnC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B,IAAI,CAAC,KAAK,EAAE,mBAAmB,GAAG,IAAI;CAUhD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,UAAU,GAAG,gBAAgB,CAGrE;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQzF"}