@@ -15,13 +15,10 @@ import {
1515 convertDateToClickhouseDateTime ,
1616} from "~/v3/eventRepository/clickhouseEventRepository.server" ;
1717import { ServiceValidationError } from "~/v3/services/baseService.server" ;
18+ import { escapeClickHouseLike , normalizeLogsSearchTerm } from "~/utils/logSearch" ;
1819
1920export type { LogLevel } ;
2021
21- function escapeClickHouseString ( val : string ) : string {
22- return val . replace ( / \\ / g, "\\\\" ) . replace ( / \/ / g, "\\/" ) . replace ( / % / g, "\\%" ) . replace ( / _ / g, "\\_" ) ;
23- }
24-
2522export type LogsListOptions = {
2623 userId ?: string ;
2724 projectId : string ;
@@ -236,15 +233,20 @@ export class LogsListPresenter extends BasePresenter {
236233 const now = new Date ( ) ;
237234 const clampedTo = effectiveTo !== undefined ? ( effectiveTo > now ? now : effectiveTo ) : now ;
238235
236+ const rawSearchTerm = search ?. trim ( ) ?? "" ;
237+ const normalizedSearchTerm =
238+ env . LOGS_SEARCH_TABLE_VERSION === "v2"
239+ ? normalizeLogsSearchTerm ( rawSearchTerm )
240+ : rawSearchTerm . toLocaleLowerCase ( ) ;
239241 const searchTerm =
240- search && search . trim ( ) !== ""
241- ? escapeClickHouseString ( search . trim ( ) ) . toLowerCase ( )
242- : undefined ;
242+ normalizedSearchTerm === "" ? undefined : escapeClickHouseLike ( normalizedSearchTerm ) ;
243243
244244 // Run exactly one bounded query. Broadening a search window is an explicit user action;
245245 // silently rescanning the same recent rows makes absence queries needlessly expensive.
246246 const runQuery = ( ) => {
247- const queryBuilder = this . clickhouse . taskEventsSearch . logsListQueryBuilder ( ) ;
247+ const queryBuilder = this . clickhouse . taskEventsSearch . logsListQueryBuilder (
248+ env . LOGS_SEARCH_TABLE_VERSION
249+ ) ;
248250
249251 // The materialized view excludes events without a trace_id; this guards the legacy tail.
250252 queryBuilder . where ( "trace_id != ''" ) ;
@@ -274,13 +276,19 @@ export class LogsListPresenter extends BasePresenter {
274276 queryBuilder . where ( "run_id = {runId: String}" , { runId } ) ;
275277 }
276278
277- // search_text is normalized and indexed as one field. A single predicate is required for
278- // ClickHouse to use the text index; the old message OR attributes predicate defeated both
279- // skip indexes.
280279 if ( searchTerm !== undefined ) {
281- queryBuilder . where ( "search_text LIKE {searchPattern: String}" , {
282- searchPattern : `%${ searchTerm } %` ,
283- } ) ;
280+ if ( env . LOGS_SEARCH_TABLE_VERSION === "v2" ) {
281+ // One predicate lets the text index answer substring searches without an OR across
282+ // independently indexed columns.
283+ queryBuilder . where ( "search_text LIKE {searchPattern: String}" , {
284+ searchPattern : `%${ searchTerm } %` ,
285+ } ) ;
286+ } else {
287+ queryBuilder . where (
288+ "(lowerUTF8(message) LIKE {searchPattern: String} OR lowerUTF8(attributes_text) LIKE {searchPattern: String})" ,
289+ { searchPattern : `%${ searchTerm } %` }
290+ ) ;
291+ }
284292 }
285293
286294 if ( levels && levels . length > 0 ) {
@@ -331,20 +339,18 @@ export class LogsListPresenter extends BasePresenter {
331339 // Limit + 1 to check if there are more results
332340 queryBuilder . limit ( effectivePageSize + 1 ) ;
333341
334- return queryBuilder . executeWithStats ( ) ;
342+ return queryBuilder . execute ( ) ;
335343 } ;
336344
337345 const [ queryError , queryResult ] = await runQuery ( ) ;
338346 if ( queryError ) {
339347 throw queryError ;
340348 }
341349
342- const records = queryResult ?. rows ?? [ ] ;
343- const partial =
344- Number ( queryResult ?. stats . read_rows ?? 0 ) >= env . CLICKHOUSE_LOGS_LIST_MAX_ROWS_TO_READ ||
345- Number ( queryResult ?. stats . elapsed_ns ?? 0 ) >=
346- env . CLICKHOUSE_LOGS_LIST_MAX_EXECUTION_TIME * 1_000_000_000 ;
347- const results = records ;
350+ // ClickHouse's break overflow modes can return a short prefix without a reliable completion
351+ // marker. Keep the default throw behavior so the product never presents truncated results as
352+ // complete.
353+ const results = queryResult ?? [ ] ;
348354 const hasMore = results . length > effectivePageSize ;
349355 const logs = results . slice ( 0 , effectivePageSize ) ;
350356
@@ -413,11 +419,10 @@ export class LogsListPresenter extends BasePresenter {
413419 } ,
414420 hasFilters,
415421 hasAnyLogs : transformedLogs . length > 0 ,
416- partial,
417422 searchTerm : search ,
418423 searchExpansion :
419424 searchTerm !== undefined && time . isDefault && transformedLogs . length === 0
420- ? { nextPeriod : "7d" as const }
425+ ? { nextPeriod : ` ${ Math . min ( retentionLimitDays ?? 7 , 7 ) } d` }
421426 : undefined ,
422427 retention :
423428 retentionLimitDays !== undefined
0 commit comments