From 9a93290a5d5f8cae429e892f2a7afd8cfa929c2b Mon Sep 17 00:00:00 2001 From: yutakobayashidev Date: Mon, 13 Jul 2026 11:08:36 +0900 Subject: [PATCH] fix: log unexpected bird failures --- src/features/posts/post-service.test.ts | 33 +++++++++++++++++++++++++ src/features/posts/post-service.ts | 13 +++++++--- src/features/posts/server-functions.ts | 27 +++++++++++--------- 3 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/features/posts/post-service.test.ts b/src/features/posts/post-service.test.ts index 2ac84a8..d8ebaaa 100644 --- a/src/features/posts/post-service.test.ts +++ b/src/features/posts/post-service.test.ts @@ -118,4 +118,37 @@ describe('searchPage', () => { }, }) }) + + it('logs unexpected details without returning them', async () => { + const client = reader() + const privateDetail = 'TASK4_PRIVATE_RELAY_MARKER_9e0b' + const upstreamError = new Error(privateDetail) + vi.mocked(client.getAllSearchResults).mockRejectedValue(upstreamError) + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + + try { + const result = await searchPage(client, { + query: 'AI', + product: 'Latest', + following: false, + }) + + expect(errorSpy).toHaveBeenCalledTimes(1) + expect(errorSpy).toHaveBeenCalledWith( + 'Unexpected Bird failure:', + upstreamError, + ) + expect(result).toEqual({ + ok: false, + error: { + code: 'upstream', + message: 'X から投稿を取得できませんでした。', + retryable: true, + }, + }) + expect(JSON.stringify(result)).not.toContain(privateDetail) + } finally { + errorSpy.mockRestore() + } + }) }) diff --git a/src/features/posts/post-service.ts b/src/features/posts/post-service.ts index 51f170f..51457e8 100644 --- a/src/features/posts/post-service.ts +++ b/src/features/posts/post-service.ts @@ -41,7 +41,13 @@ const failure = ( retryable: boolean, ): LoadResult => ({ ok: false, error: { code, message, retryable } }) -function upstreamFailure(message = ''): LoadResult { +function upstreamFailure(diagnostic: unknown = ''): LoadResult { + const message = + diagnostic instanceof Error + ? diagnostic.message + : typeof diagnostic === 'string' + ? diagnostic + : '' const lower = message.toLowerCase() if (lower.includes('timeout') || lower.includes('aborted')) { return failure('timeout', '取得がタイムアウトしました。', true) @@ -56,6 +62,7 @@ function upstreamFailure(message = ''): LoadResult { false, ) } + console.error('Unexpected Bird failure:', diagnostic) return failure('upstream', 'X から投稿を取得できませんでした。', true) } @@ -104,7 +111,7 @@ export async function loadUserPage( if (error instanceof InputError) { return failure('invalid-input', error.message, false) } - return upstreamFailure(error instanceof Error ? error.message : '') + return upstreamFailure(error) } } @@ -130,6 +137,6 @@ export async function searchPage( if (error instanceof InputError) { return failure('invalid-input', error.message, false) } - return upstreamFailure(error instanceof Error ? error.message : '') + return upstreamFailure(error) } } diff --git a/src/features/posts/server-functions.ts b/src/features/posts/server-functions.ts index 85095c8..fd79c4c 100644 --- a/src/features/posts/server-functions.ts +++ b/src/features/posts/server-functions.ts @@ -3,14 +3,17 @@ import { searchPageInputSchema, userPageInputSchema } from './inputs' import { loadUserPage, searchPage } from './post-service' import type { LoadResult } from './types' -const configFailure = (): LoadResult => ({ - ok: false, - error: { - code: 'relay-config', - message: 'TWITTER_RELAY_BASE_URL を設定してください。', - retryable: false, - }, -}) +const configFailure = (diagnostic: unknown): LoadResult => { + console.error('Bird client configuration failed:', diagnostic) + return { + ok: false, + error: { + code: 'relay-config', + message: 'TWITTER_RELAY_BASE_URL を設定してください。', + retryable: false, + }, + } +} async function reader() { const { getBirdReader } = await import('./bird-client.server') @@ -22,8 +25,8 @@ export const loadUserPosts = createServerFn({ method: 'GET' }) .handler(async ({ data }) => { try { return await loadUserPage(await reader(), data) - } catch { - return configFailure() + } catch (error) { + return configFailure(error) } }) @@ -32,7 +35,7 @@ export const searchPosts = createServerFn({ method: 'GET' }) .handler(async ({ data }) => { try { return await searchPage(await reader(), data) - } catch { - return configFailure() + } catch (error) { + return configFailure(error) } })