| ... | @@ -14,6 +14,8 @@ import * as FileSystem from "effect/FileSystem"; | ... | @@ -14,6 +14,8 @@ import * as FileSystem from "effect/FileSystem"; |
| 14 | import * as Layer from "effect/Layer"; | 14 | import * as Layer from "effect/Layer"; |
| 15 | import * as Option from "effect/Option"; | 15 | import * as Option from "effect/Option"; |
| 16 | import * as PlatformError from "effect/PlatformError"; | 16 | import * as PlatformError from "effect/PlatformError"; |
| | 17 | import * as Result from "effect/Result"; |
| | 18 | import * as Schema from "effect/Schema"; |
| 17 | | 19 | |
| 18 | interface TraceRecordLike { | 20 | interface TraceRecordLike { |
| 19 | readonly name?: unknown; | 21 | readonly name?: unknown; |
| ... | @@ -39,6 +41,19 @@ export interface TraceDiagnosticsOptions { | ... | @@ -39,6 +41,19 @@ export interface TraceDiagnosticsOptions { |
| 39 | readonly readAt?: DateTime.Utc; | 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 | export class TraceDiagnostics extends Context.Service< | 57 | export class TraceDiagnostics extends Context.Service< |
| 43 | TraceDiagnostics, | 58 | TraceDiagnostics, |
| 44 | { | 59 | { |
| ... | @@ -153,10 +168,6 @@ function isNotFoundError(error: PlatformError.PlatformError): boolean { | ... | @@ -153,10 +168,6 @@ function isNotFoundError(error: PlatformError.PlatformError): boolean { |
| 153 | return error.reason._tag === "NotFound"; | 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 | function insertBoundedSlowestSpan( | 171 | function insertBoundedSlowestSpan( |
| 161 | slowestSpans: ServerTraceDiagnosticsSpanOccurrence[], | 172 | slowestSpans: ServerTraceDiagnosticsSpanOccurrence[], |
| 162 | span: ServerTraceDiagnosticsSpanOccurrence, | 173 | span: ServerTraceDiagnosticsSpanOccurrence, |
| ... | @@ -377,22 +388,26 @@ export function aggregateTraceDiagnostics( | ... | @@ -377,22 +388,26 @@ export function aggregateTraceDiagnostics( |
| 377 | | 388 | |
| 378 | type TraceFileReadResult = | 389 | type TraceFileReadResult = |
| 379 | | { readonly _tag: "Loaded"; readonly path: string; readonly text: string } | 390 | | { readonly _tag: "Loaded"; readonly path: string; readonly text: string } |
| 380 | | { readonly _tag: "Missing"; readonly path: string } | 391 | | { readonly _tag: "Missing"; readonly path: string }; |
| 381 | | { readonly _tag: "Failed"; readonly path: string; readonly message: string }; | | |
| 382 | | 392 | |
| 383 | function readTraceFile( | 393 | function readTraceFile( |
| 384 | fileSystem: FileSystem.FileSystem, | 394 | fileSystem: FileSystem.FileSystem, |
| 385 | path: string, | 395 | path: string, |
| 386 | ): Effect.Effect<TraceFileReadResult> { | 396 | ): Effect.Effect<TraceFileReadResult, TraceFileReadError> { |
| 387 | return fileSystem.readFileString(path).pipe( | 397 | return fileSystem.readFileString(path).pipe( |
| 388 | Effect.map((text) => ({ _tag: "Loaded" as const, path, text })), | 398 | Effect.map((text): TraceFileReadResult => ({ _tag: "Loaded", path, text })), |
| 389 | Effect.catch((error: PlatformError.PlatformError) => | 399 | Effect.catchTags({ |
| 390 | Effect.succeed( | 400 | PlatformError: (cause) => |
| 391 | isNotFoundError(error) | 401 | isNotFoundError(cause) |
| 392 | ? { _tag: "Missing" as const, path } | 402 | ? Effect.succeed<TraceFileReadResult>({ _tag: "Missing", path }) |
| 393 | : { _tag: "Failed" as const, path, message: platformErrorMessage(error) }, | 403 | : Effect.fail( |
| 394 | ), | 404 | new TraceFileReadError({ |
| 395 | ), | 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,19 +420,34 @@ export const make = Effect.gen(function* () { |
| 405 | const slowSpanThresholdMs = options.slowSpanThresholdMs ?? DEFAULT_SLOW_SPAN_THRESHOLD_MS; | 420 | const slowSpanThresholdMs = options.slowSpanThresholdMs ?? DEFAULT_SLOW_SPAN_THRESHOLD_MS; |
| 406 | const paths = toRotatedTracePaths(options.traceFilePath, options.maxFiles); | 421 | const paths = toRotatedTracePaths(options.traceFilePath, options.maxFiles); |
| 407 | const results = yield* Effect.all( | 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 | concurrency: 1, | 438 | concurrency: 1, |
| 411 | }, | 439 | }, |
| 412 | ); | 440 | ); |
| 413 | const files = results.flatMap((result) => | 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 | const readFailureError = readFailure | 447 | const readFailureError = readFailure |
| 418 | ? ({ | 448 | ? ({ |
| 419 | kind: "trace-file-read-failed", | 449 | kind: "trace-file-read-failed", |
| 420 | message: readFailure.message.trim() || `Failed to read ${readFailure.path}.`, | 450 | message: readFailure.failure.message, |
| 421 | } satisfies TraceDiagnosticsErrorSummary) | 451 | } satisfies TraceDiagnosticsErrorSummary) |
| 422 | : undefined; | 452 | : undefined; |
| 423 | | 453 | |