Skip to content

Commit 2e58162

Browse files
committed
improvement(logs): drop casts and test-only exports from the failure log
Reads status and cause without casts, keeps the thrown missing-required-fields error's name and stack unchanged, ignores an empty execution id when scoping a logged mark, unexports wasFailureLogged and FailureKind (tests observe the outer boundary through logFailureOnce instead), and shrinks the explicit-any baseline for the Agent handler.
1 parent 934d122 commit 2e58162

12 files changed

Lines changed: 53 additions & 44 deletions

File tree

‎apps/sim/executor/execution/block-executor.test.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import { createLogger } from '@sim/logger'
12
import { loggerMock } from '@sim/testing'
23
import { maskClientMock, maskClientMockFns } from '@sim/testing/mocks/mask-client.mock'
34
import { permissionCheckMock } from '@sim/testing/mocks/permission-check.mock'
45
import { storageServiceMockFns } from '@sim/testing/mocks/storage-service.mock'
56
import { uploadsMock } from '@sim/testing/mocks/uploads.mock'
67
import { DrizzleQueryError } from 'drizzle-orm/errors'
78
import { beforeEach, describe, expect, it, vi } from 'vitest'
8-
import { classifyFailure, wasFailureLogged } from '@/lib/core/errors/failure-log'
9+
import { classifyFailure, logFailureOnce } from '@/lib/core/errors/failure-log'
910
import { clearLargeValueCacheForTests } from '@/lib/execution/payloads/cache'
1011
import { createLargeArrayManifest } from '@/lib/execution/payloads/large-array-manifest'
1112
import { isLargeValueRef } from '@/lib/execution/payloads/large-value-ref'
@@ -500,7 +501,7 @@ describe('BlockExecutor', () => {
500501
expect(JSON.stringify(logged)).not.toContain('owner-secret-id')
501502
/** Logged here at error, so the engine and the run surfaces must see it as already logged. */
502503
expect(classifyFailure(thrown)).toBe('internal')
503-
expect(wasFailureLogged(thrown)).toBe(true)
504+
expect(logFailureOnce(createLogger('OuterBoundary'), 'probe', thrown)).toBeUndefined()
504505
})
505506

506507
it('fires block completion callbacks for pausing blocks so clients receive pause output', async () => {

‎apps/sim/executor/execution/failure-trace.test.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import { createLogger } from '@sim/logger'
12
import { permissionCheckMock } from '@sim/testing/mocks/permission-check.mock'
23
import { describe, expect, it, vi } from 'vitest'
3-
import { classifyFailure, wasFailureLogged } from '@/lib/core/errors/failure-log'
4+
import { classifyFailure, logFailureOnce } from '@/lib/core/errors/failure-log'
45
import { buildTraceSpans } from '@/lib/logs/execution/trace-spans/trace-spans'
56
import { DAGExecutor } from '@/executor/execution/executor'
67
import { hasExecutionResult } from '@/executor/utils/errors'
@@ -87,7 +88,7 @@ describe('failed run trace', () => {
8788
* The block executor logged it; the block wrap and the engine's rethrow must keep that
8889
* visible so execution-core and the trigger surfaces do not log it again.
8990
*/
90-
expect(wasFailureLogged(thrown)).toBe(true)
91+
expect(logFailureOnce(createLogger('OuterBoundary'), 'probe', thrown)).toBeUndefined()
9192
expect(classifyFailure(thrown)).toBe('user')
9293
})
9394
})

‎apps/sim/executor/handlers/agent/agent-handler.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createLogger } from '@sim/logger'
22
import { getErrorMessage, toError } from '@sim/utils/errors'
3-
import { isPlainRecord, omit } from '@sim/utils/object'
3+
import { isPlainRecord, isRecordLike, omit } from '@sim/utils/object'
44
import { truncate } from '@sim/utils/string'
55
import { markFailureKind } from '@/lib/core/errors/failure-log'
66
import { normalizeStringRecord, normalizeWorkflowVariables } from '@/lib/core/utils/records'
@@ -328,7 +328,7 @@ function describeProviderTransportFailure(error: Error): string | null {
328328
}
329329

330330
function isProviderKeyRejection(error: unknown): boolean {
331-
const status = (error as { status?: unknown } | null)?.status
331+
const status = isRecordLike(error) ? error.status : undefined
332332
return status === 401 || status === 402 || status === 403
333333
}
334334

‎apps/sim/executor/handlers/workflow/workflow-handler.test.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { createLogger } from '@sim/logger'
12
import { encryptionMockFns, environmentUtilsMockFns, resetEnvironmentUtilsMock } from '@sim/testing'
23
import { createSessionPrincipal } from '@sim/testing/factories/principal.factory'
34
import { authInternalMock, authInternalMockFns } from '@sim/testing/mocks/auth-internal.mock'
@@ -18,7 +19,7 @@ import {
1819
import { permissionsMock } from '@sim/testing/mocks/permissions.mock'
1920
import { usersQueriesMock, usersQueriesMockFns } from '@sim/testing/mocks/users-queries.mock'
2021
import { afterAll, beforeAll, beforeEach, describe, expect, it, type Mock, vi } from 'vitest'
21-
import { classifyFailure, wasFailureLogged } from '@/lib/core/errors/failure-log'
22+
import { classifyFailure, logFailureOnce } from '@/lib/core/errors/failure-log'
2223
import { createTimeoutAbortController, getExecutionDeadlineAt } from '@/lib/core/execution-limits'
2324
import { OrchestrationError } from '@/lib/core/orchestration/types'
2425
import { getBlock } from '@/blocks/registry'
@@ -382,7 +383,7 @@ describe('WorkflowBlockHandler', () => {
382383

383384
expect(thrown).toBeInstanceOf(Error)
384385
/** Logged here, the block executor would skip its log carrying block, run, and stack. */
385-
expect(wasFailureLogged(thrown)).toBe(false)
386+
expect(logFailureOnce(createLogger('OuterBoundary'), 'probe', thrown)).toBe('internal')
386387
expect(classifyFailure(thrown)).toBe('internal')
387388
})
388389

‎apps/sim/executor/handlers/workflow/workflow-handler.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1575,7 +1575,7 @@ export class WorkflowBlockHandler implements BlockHandler {
15751575
childWorkflowSnapshotId,
15761576
childWorkflowInstanceId: instanceId,
15771577
})
1578-
/** The child run logged its own failure; the warning above names it for this run. */
1578+
/** The warning above is this failure's log line. */
15791579
markFailureLogged(childFailure)
15801580
throw childFailure
15811581
}

‎apps/sim/lib/core/errors/failure-log.test.ts‎

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,18 @@ import {
77
logFailureOnce,
88
markFailureKind,
99
markFailureLogged,
10-
wasFailureLogged,
1110
} from '@/lib/core/errors/failure-log'
1211
import { RetryableSetupError } from '@/lib/core/errors/retryable-infrastructure'
1312
import { UserFailure } from '@/lib/core/errors/user-failure'
1413
import { HostedKeyRateLimitedError, HostedKeyUnavailableError } from '@/tools/errors'
1514

15+
const logger = createLogger('FailureLogTest')
16+
17+
/** What an outer boundary does with the failure: the kind it logs at, or undefined when it skips. */
18+
function outerBoundary(error: unknown, executionId?: string) {
19+
return logFailureOnce(logger, 'probe', error, { executionId })
20+
}
21+
1622
describe('classifyFailure', () => {
1723
it('keeps a database failure internal even beneath a user mark', () => {
1824
const queryError = new DrizzleQueryError('select 1', [], new Error('connection reset'))
@@ -52,8 +58,7 @@ describe('classifyFailure', () => {
5258
const first = new Error('first')
5359
const second = new Error('second', { cause: first })
5460
Object.assign(first, { cause: second })
55-
expect(classifyFailure(first)).toBe('internal')
56-
expect(wasFailureLogged(first)).toBe(false)
61+
expect(outerBoundary(first)).toBe('internal')
5762
})
5863
})
5964

@@ -62,37 +67,35 @@ describe('inheritFailureMarks', () => {
6267
const logged = new Error('child failed')
6368
markFailureLogged(logged)
6469
const boundary = inheritFailureMarks(new Error('Custom block execution failed'), logged)
65-
expect(wasFailureLogged(boundary)).toBe(true)
70+
expect(outerBoundary(boundary)).toBeUndefined()
6671
expect(classifyFailure(boundary)).toBe('internal')
6772

6873
const authored = inheritFailureMarks(new Error('wrapped'), new UserFailure('missing input'))
69-
expect(wasFailureLogged(authored)).toBe(false)
70-
expect(classifyFailure(authored)).toBe('user')
74+
expect(outerBoundary(authored)).toBe('user')
7175
})
7276
})
7377

74-
describe('wasFailureLogged', () => {
78+
describe('logFailureOnce', () => {
7579
it('marks a frozen error, which a property write would throw on', () => {
7680
const frozen = Object.freeze(new Error('frozen'))
7781
markFailureLogged(frozen)
7882
markFailureKind(frozen, 'user')
79-
expect(wasFailureLogged(frozen)).toBe(true)
83+
expect(outerBoundary(frozen)).toBeUndefined()
8084
expect(classifyFailure(frozen)).toBe('user')
8185
})
8286

8387
it('does not treat an unrelated error as logged', () => {
8488
markFailureLogged(new Error('logged elsewhere'))
85-
expect(wasFailureLogged(new Error('fresh'))).toBe(false)
89+
expect(outerBoundary(new Error('fresh'))).toBe('internal')
8690
})
8791

8892
it('scopes a raw value logged at an execution boundary to that execution only', () => {
8993
const persistentFault = new Error('module failed to load')
90-
logFailureOnce(createLogger('FailureLogTest'), 'Execution failed', persistentFault, {
91-
executionId: 'exec-1',
92-
})
94+
expect(outerBoundary(persistentFault, 'exec-1')).toBe('internal')
9395

94-
expect(wasFailureLogged(persistentFault, 'exec-1')).toBe(true)
95-
expect(wasFailureLogged(persistentFault, 'exec-2')).toBe(false)
96-
expect(wasFailureLogged(persistentFault)).toBe(false)
96+
expect(outerBoundary(persistentFault, 'exec-1')).toBeUndefined()
97+
expect(outerBoundary(persistentFault, 'exec-2')).toBe('internal')
98+
expect(outerBoundary(persistentFault)).toBe('internal')
99+
expect(outerBoundary(persistentFault, '')).toBe('internal')
97100
})
98101
})

‎apps/sim/lib/core/errors/failure-log.ts‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ import { HttpError } from '@/lib/core/utils/http-error'
1616
* in-process operations are attributed explicitly at their boundary and never land here.
1717
* - `internal`: Sim's own fault, or a cause nothing attributed. Always logged at error.
1818
*/
19-
export type FailureKind = 'user' | 'third_party_client' | 'third_party_server' | 'internal'
19+
type FailureKind = 'user' | 'third_party_client' | 'third_party_server' | 'internal'
2020

21-
/** Mirrors the cause depth `describeError` and `readStatusCode` follow. */
21+
/** Same bound `readStatusCode` puts on its cause walk. */
2222
const MAX_CAUSE_DEPTH = 8
2323

2424
/**
@@ -45,7 +45,7 @@ function causeChain(error: unknown): object[] {
4545
let current = error
4646
while (isKeyable(current) && chain.length < MAX_CAUSE_DEPTH && !chain.includes(current)) {
4747
chain.push(current)
48-
current = (current as { cause?: unknown }).cause
48+
current = 'cause' in current ? current.cause : undefined
4949
}
5050
return chain
5151
}
@@ -75,7 +75,7 @@ export function classifyFailure(error: unknown): FailureKind {
7575
return link.statusCode >= 400 && link.statusCode < 500 ? 'user' : 'internal'
7676
}
7777

78-
const status = (link as { status?: unknown }).status
78+
const status = 'status' in link ? link.status : undefined
7979
if (typeof status === 'number' && status >= 400 && status < 600) {
8080
return status >= 500 ? 'third_party_server' : 'third_party_client'
8181
}
@@ -119,11 +119,11 @@ export function adoptToolFailure<T>(error: T, result: { output?: unknown }): T {
119119
* Whether a boundary already logged this failure: a logged carrier anywhere in the cause chain,
120120
* or, given `executionId`, a raw value that boundary logged during this same execution.
121121
*/
122-
export function wasFailureLogged(error: unknown, executionId?: string): boolean {
122+
function wasFailureLogged(error: unknown, executionId?: string): boolean {
123123
return causeChain(error).some(
124124
(link) =>
125125
loggedCarriers.has(link) ||
126-
(executionId !== undefined && loggedInExecution.get(link) === executionId)
126+
(Boolean(executionId) && loggedInExecution.get(link) === executionId)
127127
)
128128
}
129129

@@ -159,10 +159,11 @@ export function logFailureOnce(
159159
const failureKind = classifyFailure(error)
160160
const fields = typeof metadata === 'function' ? metadata() : metadata
161161
logger[LOG_LEVEL_BY_KIND[failureKind]](message, {
162-
...(executionId !== undefined ? { executionId } : {}),
162+
...(executionId ? { executionId } : {}),
163163
...fields,
164164
failureKind,
165165
})
166-
if (executionId !== undefined && isKeyable(error)) loggedInExecution.set(error, executionId)
166+
/** An empty id (a request that failed before minting one) scopes nothing. */
167+
if (executionId && isKeyable(error)) loggedInExecution.set(error, executionId)
167168
return failureKind
168169
}

‎apps/sim/serializer/errors.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,5 @@ import { UserFailure } from '@/lib/core/errors/user-failure'
44
export class MissingRequiredFieldsError extends UserFailure {
55
constructor(blockName: string, missingFields: string[]) {
66
super(`${blockName} is missing required fields: ${missingFields.join(', ')}`)
7-
this.name = 'MissingRequiredFieldsError'
87
}
98
}

‎apps/sim/serializer/index.test.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -369,8 +369,10 @@ describe('Serializer', () => {
369369
} catch (error) {
370370
refusal = error
371371
}
372-
expect(refusal).toBeInstanceOf(Error)
373-
expect((refusal as Error).message).toBe('Test Jina Block is missing required fields: API Key')
372+
expect(refusal).toMatchObject({
373+
name: 'Error',
374+
message: 'Test Jina Block is missing required fields: API Key',
375+
})
374376
/** The author's configuration, so execution logs it at info rather than paging at error. */
375377
expect(classifyFailure(refusal)).toBe('user')
376378
})

‎apps/sim/tools/index.test.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { createLogger } from '@sim/logger'
12
import { createSessionPrincipal } from '@sim/testing/factories/principal.factory'
23
import { jsonResponse } from '@sim/testing/helpers/http'
34
import { apiKeyByokMock, apiKeyByokMockFns } from '@sim/testing/mocks/api-key-byok.mock'
@@ -420,7 +421,7 @@ vi.mock('@/tools/utils.server', async (importOriginal) => {
420421
})
421422

422423
import type { QueryClient } from '@tanstack/react-query'
423-
import { adoptToolFailure, classifyFailure, wasFailureLogged } from '@/lib/core/errors/failure-log'
424+
import { adoptToolFailure, classifyFailure, logFailureOnce } from '@/lib/core/errors/failure-log'
424425
import * as getQueryClientModule from '@/app/_shell/providers/get-query-client'
425426
import { ApiBlockHandler } from '@/executor/handlers/api/api-handler'
426427
import { buildBlockExecutionError } from '@/executor/utils/errors'
@@ -5923,7 +5924,7 @@ describe('Centralized Error Handling', () => {
59235924
async (status, kind) => {
59245925
const blockError = await failApiBlock(jsonResponse({ error: 'rejected' }, status))
59255926
expect(classifyFailure(blockError)).toBe(kind)
5926-
expect(wasFailureLogged(blockError)).toBe(true)
5927+
expect(logFailureOnce(createLogger('OuterBoundary'), 'probe', blockError)).toBeUndefined()
59275928
}
59285929
)
59295930

@@ -5942,7 +5943,7 @@ describe('Centralized Error Handling', () => {
59425943
try {
59435944
const blockError = await failApiBlock(jsonResponse({ ok: false }))
59445945
expect(classifyFailure(blockError)).toBe(kind)
5945-
expect(wasFailureLogged(blockError)).toBe(true)
5946+
expect(logFailureOnce(createLogger('OuterBoundary'), 'probe', blockError)).toBeUndefined()
59465947
} finally {
59475948
tools.http_request.transformResponse = originalTransform
59485949
}

0 commit comments

Comments
 (0)