Skip to content

Commit 2610ee8

Browse files
bennyliiDeepSeek V4 Pro
andcommitted
fix: restore correct log level filtering and add request log coverage
- server.ts: assign correct log level (error/warn/info) based on status code and latency, letting shouldRecordLog filter by user's configured logLevel - completions.ts: add request log recording for success, error, and exception paths in the legacy /v1/completions route - logsStore.ts: extract filterLogs() pure function to eliminate filter logic duplication between applyFilter() and refresh() - tests: add log-buffering tests for LogManager and StoreManager patterns Co-Authored-By: DeepSeek V4 Pro <noreply@deepseek.com>
1 parent 063dc71 commit 2610ee8

4 files changed

Lines changed: 414 additions & 46 deletions

File tree

src/main/proxy/routes/completions.ts

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import Router from '@koa/router'
77
import type { Context } from 'koa'
8+
import { PassThrough } from 'stream'
89
import { loadBalancer } from '../loadbalancer'
910
import { requestForwarder } from '../forwarder'
1011
import { streamHandler } from '../stream'
@@ -33,6 +34,16 @@ function generateRequestId(): string {
3334
return `cmpl-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
3435
}
3536

37+
/**
38+
* Extract user input from prompt
39+
*/
40+
function extractUserInput(prompt: string | string[]): string | undefined {
41+
if (Array.isArray(prompt)) {
42+
return prompt.filter(p => p).join(' ')
43+
}
44+
return prompt || undefined
45+
}
46+
3647
/**
3748
* Convert prompt to messages format
3849
*/
@@ -159,6 +170,37 @@ router.post('/completions', async (ctx: Context) => {
159170
type: 'api_error',
160171
},
161172
}
173+
174+
storeManager.addLog('error', `Request failed: ${result.error}`, {
175+
requestId,
176+
providerId: provider.id,
177+
accountId: account.id,
178+
model: request.model,
179+
latency,
180+
})
181+
182+
storeManager.addRequestLog({
183+
timestamp: startTime,
184+
status: 'error',
185+
statusCode: result.status || 500,
186+
method: 'POST',
187+
url: '/v1/completions',
188+
model: request.model,
189+
actualModel,
190+
providerId: provider.id,
191+
providerName: provider.name,
192+
accountId: account.id,
193+
accountName: account.name,
194+
requestBody: JSON.stringify(request),
195+
userInput: extractUserInput(request.prompt),
196+
responseStatus: result.status || 500,
197+
latency,
198+
isStream: request.stream || false,
199+
errorMessage: result.error,
200+
})
201+
202+
storeManager.recordRequestInStats(false, latency, request.model, provider.id, account.id)
203+
162204
return
163205
}
164206

@@ -180,14 +222,82 @@ router.post('/completions', async (ctx: Context) => {
180222
isStream: request.stream,
181223
})
182224

225+
const userInput = extractUserInput(request.prompt)
226+
const responseBodyForLog = !request.stream && result.body
227+
? JSON.stringify(result.body)
228+
: undefined
229+
230+
let logEntryId: string | undefined
231+
232+
if (!request.stream) {
233+
const logEntry = storeManager.addRequestLog({
234+
timestamp: startTime,
235+
status: 'success',
236+
statusCode: 200,
237+
method: 'POST',
238+
url: '/v1/completions',
239+
model: request.model,
240+
actualModel,
241+
providerId: provider.id,
242+
providerName: provider.name,
243+
accountId: account.id,
244+
accountName: account.name,
245+
requestBody: JSON.stringify(request),
246+
userInput,
247+
responseStatus: 200,
248+
responseBody: responseBodyForLog,
249+
latency,
250+
isStream: false,
251+
})
252+
logEntryId = logEntry.id
253+
} else {
254+
const logEntry = storeManager.addRequestLog({
255+
timestamp: startTime,
256+
status: 'success',
257+
statusCode: 200,
258+
method: 'POST',
259+
url: '/v1/completions',
260+
model: request.model,
261+
actualModel,
262+
providerId: provider.id,
263+
providerName: provider.name,
264+
accountId: account.id,
265+
accountName: account.name,
266+
requestBody: JSON.stringify(request),
267+
userInput,
268+
responseStatus: 200,
269+
latency,
270+
isStream: true,
271+
})
272+
logEntryId = logEntry.id
273+
}
274+
275+
storeManager.recordRequestInStats(true, latency, request.model, provider.id, account.id)
276+
183277
if (request.stream && result.stream) {
184278
ctx.set('Content-Type', 'text/event-stream')
185279
ctx.set('Cache-Control', 'no-cache')
186280
ctx.set('Connection', 'keep-alive')
187281
ctx.set('X-Accel-Buffering', 'no')
188282

189283
const transformStream = streamHandler.createTransformStream(actualModel, requestId)
284+
285+
// Collect stream content for log update
286+
let collectedContent = ''
287+
transformStream.on('data', (chunk: Buffer) => {
288+
collectedContent += chunk.toString()
289+
})
290+
190291
result.stream.pipe(transformStream)
292+
293+
transformStream.once('end', () => {
294+
if (logEntryId) {
295+
storeManager.updateRequestLog(logEntryId, {
296+
responseBody: collectedContent || undefined,
297+
})
298+
}
299+
})
300+
191301
ctx.body = transformStream
192302
} else {
193303
ctx.set('Content-Type', 'application/json')
@@ -197,13 +307,46 @@ router.post('/completions', async (ctx: Context) => {
197307
const latency = Date.now() - startTime
198308
proxyStatusManager.recordRequestFailure(latency)
199309

310+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
311+
200312
ctx.status = 500
201313
ctx.body = {
202314
error: {
203-
message: error instanceof Error ? error.message : 'Unknown error',
315+
message: errorMessage,
204316
type: 'internal_error',
205317
},
206318
}
319+
320+
storeManager.addLog('error', `Request exception: ${errorMessage}`, {
321+
requestId,
322+
providerId: provider.id,
323+
accountId: account.id,
324+
model: request.model,
325+
latency,
326+
error: errorMessage,
327+
})
328+
329+
storeManager.addRequestLog({
330+
timestamp: startTime,
331+
status: 'error',
332+
statusCode: 500,
333+
method: 'POST',
334+
url: '/v1/completions',
335+
model: request.model,
336+
actualModel,
337+
providerId: provider.id,
338+
providerName: provider.name,
339+
accountId: account.id,
340+
accountName: account.name,
341+
requestBody: JSON.stringify(request),
342+
userInput: extractUserInput(request.prompt),
343+
responseStatus: 500,
344+
latency,
345+
isStream: request.stream || false,
346+
errorMessage,
347+
})
348+
349+
storeManager.recordRequestInStats(false, latency, request.model, provider.id, account.id)
207350
}
208351
})
209352

src/main/proxy/server.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,14 @@ export class ProxyServer {
131131
await next()
132132

133133
const latency = Date.now() - startTime
134-
const shouldRecordAccessLog =
135-
!ctx.path.startsWith('/v1/models') &&
136-
(ctx.status >= 400 || latency >= SLOW_REQUEST_THRESHOLD_MS)
137134

138-
if (shouldRecordAccessLog) {
139-
storeManager.addLog('warn', `${ctx.method} ${ctx.path} ${ctx.status} ${latency}ms`, {
135+
if (!ctx.path.startsWith('/v1/models')) {
136+
const level =
137+
ctx.status >= 400 ? 'error' :
138+
latency >= SLOW_REQUEST_THRESHOLD_MS ? 'warn' :
139+
'info'
140+
141+
storeManager.addLog(level, `${ctx.method} ${ctx.path} ${ctx.status} ${latency}ms`, {
140142
data: {
141143
method: ctx.method,
142144
path: ctx.path,

src/renderer/src/stores/logsStore.ts

Lines changed: 27 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,31 @@ interface LogsState {
5151
refresh: () => Promise<void>
5252
}
5353

54+
function filterLogs(logs: LogEntry[], filter: LogFilter): LogEntry[] {
55+
let filtered = [...logs]
56+
57+
if (filter.level !== 'all') {
58+
filtered = filtered.filter((log) => log.level === filter.level)
59+
}
60+
61+
if (filter.keyword) {
62+
const keyword = filter.keyword.toLowerCase()
63+
filtered = filtered.filter((log) =>
64+
log.message.toLowerCase().includes(keyword)
65+
)
66+
}
67+
68+
if (filter.startTime) {
69+
filtered = filtered.filter((log) => log.timestamp >= filter.startTime!)
70+
}
71+
72+
if (filter.endTime) {
73+
filtered = filtered.filter((log) => log.timestamp <= filter.endTime!)
74+
}
75+
76+
return filtered
77+
}
78+
5479
export const useLogsStore = create<LogsState>((set, get) => ({
5580
logs: [],
5681
filteredLogs: [],
@@ -130,28 +155,7 @@ export const useLogsStore = create<LogsState>((set, get) => ({
130155

131156
applyFilter: () => {
132157
const { logs, filter } = get()
133-
let filtered = [...logs]
134-
135-
if (filter.level !== 'all') {
136-
filtered = filtered.filter((log) => log.level === filter.level)
137-
}
138-
139-
if (filter.keyword) {
140-
const keyword = filter.keyword.toLowerCase()
141-
filtered = filtered.filter((log) =>
142-
log.message.toLowerCase().includes(keyword)
143-
)
144-
}
145-
146-
if (filter.startTime) {
147-
filtered = filtered.filter((log) => log.timestamp >= filter.startTime!)
148-
}
149-
150-
if (filter.endTime) {
151-
filtered = filtered.filter((log) => log.timestamp <= filter.endTime!)
152-
}
153-
154-
set({ filteredLogs: filtered })
158+
set({ filteredLogs: filterLogs(logs, filter) })
155159
},
156160

157161
clearLogs: () => {
@@ -209,26 +213,9 @@ export const useLogsStore = create<LogsState>((set, get) => ({
209213
window.electronAPI.logs.getTrend(7),
210214
])
211215

212-
let filteredLogs = [...logs]
213-
if (filter.level !== 'all') {
214-
filteredLogs = filteredLogs.filter((log) => log.level === filter.level)
215-
}
216-
if (filter.keyword) {
217-
const keyword = filter.keyword.toLowerCase()
218-
filteredLogs = filteredLogs.filter((log) =>
219-
log.message.toLowerCase().includes(keyword)
220-
)
221-
}
222-
if (filter.startTime) {
223-
filteredLogs = filteredLogs.filter((log) => log.timestamp >= filter.startTime!)
224-
}
225-
if (filter.endTime) {
226-
filteredLogs = filteredLogs.filter((log) => log.timestamp <= filter.endTime!)
227-
}
228-
229216
set({
230217
logs,
231-
filteredLogs,
218+
filteredLogs: filterLogs(logs, filter),
232219
stats,
233220
trend,
234221
hasMore: logs.length >= pageSize,

0 commit comments

Comments
 (0)