| ... | ... | @@ -14,6 +14,8 @@ import * as FileSystem from "effect/FileSystem"; |
| 14 | 14 | import * as Layer from "effect/Layer"; |
| 15 | 15 | import * as Option from "effect/Option"; |
| 16 | 16 | import * as PlatformError from "effect/PlatformError"; |
| 17 | import * as Result from "effect/Result"; |
| 18 | import * as Schema from "effect/Schema"; |
| 17 | 19 | |
| 18 | 20 | interface TraceRecordLike { |
| 19 | 21 | readonly name?: unknown; |
| ... | ... | @@ -39,6 +41,19 @@ export interface TraceDiagnosticsOptions { |
| 39 | 41 | readonly readAt?: DateTime.Utc; |
| 40 | 42 | } |
| 41 | 43 | |
| 44 | export class TraceFileReadError extends Schema.TaggedErrorClass<TraceFileReadError>()( |
| 45 | "TraceFileReadError", |
| 46 | { |
| 47 | traceFilePath: Schema.String, |
| 48 | causeTag: Schema.String, |
| 49 | cause: Schema.Defect(), |
| 50 | }, |
| 51 | ) { |
| 52 | override get message(): string { |
| 53 | return `Failed to read local trace file '${this.traceFilePath}'.`; |
| 54 | } |
| 55 | } |
| 56 | |
| 42 | 57 | export class TraceDiagnostics extends Context.Service< |
| 43 | 58 | TraceDiagnostics, |
| 44 | 59 | { |
| ... | ... | @@ -153,10 +168,6 @@ function isNotFoundError(error: PlatformError.PlatformError): boolean { |
| 153 | 168 | return error.reason._tag === "NotFound"; |
| 154 | 169 | } |
| 155 | 170 | |
| 156 | | function platformErrorMessage(error: PlatformError.PlatformError): string { |
| 157 | | return error.message || String(error); |
| 158 | | } |
| 159 | | |
| 160 | 171 | function insertBoundedSlowestSpan( |
| 161 | 172 | slowestSpans: ServerTraceDiagnosticsSpanOccurrence[], |
| 162 | 173 | span: ServerTraceDiagnosticsSpanOccurrence, |
| ... | ... | @@ -377,22 +388,26 @@ export function aggregateTraceDiagnostics( |
| 377 | 388 | |
| 378 | 389 | type TraceFileReadResult = |
| 379 | 390 | | { readonly _tag: "Loaded"; readonly path: string; readonly text: string } |
| 380 | | | { readonly _tag: "Missing"; readonly path: string } |
| 381 | | | { readonly _tag: "Failed"; readonly path: string; readonly message: string }; |
| 391 | | { readonly _tag: "Missing"; readonly path: string }; |
| 382 | 392 | |
| 383 | 393 | function readTraceFile( |
| 384 | 394 | fileSystem: FileSystem.FileSystem, |
| 385 | 395 | path: string, |
| 386 | | ): Effect.Effect<TraceFileReadResult> { |
| 396 | ): Effect.Effect<TraceFileReadResult, TraceFileReadError> { |
| 387 | 397 | return fileSystem.readFileString(path).pipe( |
| 388 | | Effect.map((text) => ({ _tag: "Loaded" as const, path, text })), |
| 389 | | Effect.catch((error: PlatformError.PlatformError) => |
| 390 | | Effect.succeed( |
| 391 | | isNotFoundError(error) |
| 392 | | ? { _tag: "Missing" as const, path } |
| 393 | | : { _tag: "Failed" as const, path, message: platformErrorMessage(error) }, |
| 394 | | ), |
| 395 | | ), |
| 398 | Effect.map((text): TraceFileReadResult => ({ _tag: "Loaded", path, text })), |
| 399 | Effect.catchTags({ |
| 400 | PlatformError: (cause) => |
| 401 | isNotFoundError(cause) |
| 402 | ? Effect.succeed<TraceFileReadResult>({ _tag: "Missing", path }) |
| 403 | : Effect.fail( |
| 404 | new TraceFileReadError({ |
| 405 | traceFilePath: path, |
| 406 | causeTag: cause.reason._tag, |
| 407 | cause, |
| 408 | }), |
| 409 | ), |
| 410 | }), |
| 396 | 411 | ); |
| 397 | 412 | } |
| 398 | 413 | |
| ... | ... | @@ -405,19 +420,34 @@ export const make = Effect.gen(function* () { |
| 405 | 420 | const slowSpanThresholdMs = options.slowSpanThresholdMs ?? DEFAULT_SLOW_SPAN_THRESHOLD_MS; |
| 406 | 421 | const paths = toRotatedTracePaths(options.traceFilePath, options.maxFiles); |
| 407 | 422 | const results = yield* Effect.all( |
| 408 | | paths.map((path) => readTraceFile(fileSystem, path)), |
| 423 | paths.map((path) => |
| 424 | readTraceFile(fileSystem, path).pipe( |
| 425 | Effect.tapError((cause) => |
| 426 | Effect.logWarning("Failed to read local trace file.").pipe( |
| 427 | Effect.annotateLogs({ |
| 428 | traceFilePath: cause.traceFilePath, |
| 429 | errorTag: cause._tag, |
| 430 | causeTag: cause.causeTag, |
| 431 | }), |
| 432 | ), |
| 433 | ), |
| 434 | Effect.result, |
| 435 | ), |
| 436 | ), |
| 409 | 437 | { |
| 410 | 438 | concurrency: 1, |
| 411 | 439 | }, |
| 412 | 440 | ); |
| 413 | 441 | const files = results.flatMap((result) => |
| 414 | | result._tag === "Loaded" ? [{ path: result.path, text: result.text }] : [], |
| 442 | Result.isSuccess(result) && result.success._tag === "Loaded" |
| 443 | ? [{ path: result.success.path, text: result.success.text }] |
| 444 | : [], |
| 415 | 445 | ); |
| 416 | | const readFailure = results.find((result) => result._tag === "Failed"); |
| 446 | const readFailure = results.find(Result.isFailure); |
| 417 | 447 | const readFailureError = readFailure |
| 418 | 448 | ? ({ |
| 419 | 449 | kind: "trace-file-read-failed", |
| 420 | | message: readFailure.message.trim() || `Failed to read ${readFailure.path}.`, |
| 450 | message: readFailure.failure.message, |
| 421 | 451 | } satisfies TraceDiagnosticsErrorSummary) |
| 422 | 452 | : undefined; |
| 423 | 453 | |