Skip to content

Commit 941dbaa

Browse files
committed
show info from attributes as message for error spans
1 parent 53274b7 commit 941dbaa

File tree

2 files changed

+44
-46
lines changed

2 files changed

+44
-46
lines changed

apps/webapp/app/presenters/v3/LogsListPresenter.server.ts

Lines changed: 38 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ function decodeCursor(cursor: string): LogCursor | null {
7979

8080
// Convert ClickHouse kind to display level
8181
function kindToLevel(kind: string, status: string): LogLevel {
82-
// CANCELLED status takes precedence
8382
if (status === "CANCELLED") {
8483
return "CANCELLED";
8584
}
@@ -127,7 +126,7 @@ function levelToKindsAndStatuses(
127126
}
128127
}
129128

130-
// Convert nanoseconds to milliseconds
129+
131130
function convertDateToNanoseconds(date: Date): bigint {
132131
return BigInt(date.getTime()) * 1_000_000n;
133132
}
@@ -161,21 +160,17 @@ export class LogsListPresenter {
161160
search,
162161
from,
163162
to,
164-
direction = "forward",
165163
cursor,
166164
pageSize = DEFAULT_PAGE_SIZE,
167165
includeDebugLogs = true,
168166
}: LogsListOptions
169167
) {
170-
// Get time values from raw values (including default period)
171168
const time = timeFilters({
172169
period,
173170
from,
174171
to,
175172
});
176173

177-
// If period is provided but from/to are not calculated, convert period to date range
178-
// This is needed because timeFilters doesn't convert period to actual dates
179174
let effectiveFrom = time.from;
180175
let effectiveTo = time.to;
181176

@@ -208,13 +203,11 @@ export class LogsListPresenter {
208203
(search !== undefined && search !== "") ||
209204
!time.isDefault;
210205

211-
// Get all possible tasks
212206
const possibleTasksAsync = getAllTaskIdentifiers(
213207
this.replica,
214208
environmentId
215209
);
216210

217-
// Get possible bulk actions
218211
const bulkActionsAsync = this.replica.bulkActionGroup.findMany({
219212
select: {
220213
friendlyId: true,
@@ -239,7 +232,6 @@ export class LogsListPresenter {
239232
findDisplayableEnvironment(environmentId, userId),
240233
]);
241234

242-
// If the bulk action isn't in the most recent ones, add it separately
243235
if (
244236
bulkId &&
245237
!bulkActions.some((bulkAction) => bulkAction.friendlyId === bulkId)
@@ -306,7 +298,6 @@ export class LogsListPresenter {
306298
},
307299
});
308300

309-
// If no matching runs, return empty result
310301
if (runIds.length === 0) {
311302
return {
312303
logs: [],
@@ -341,15 +332,12 @@ export class LogsListPresenter {
341332
}
342333
}
343334

344-
// Build ClickHouse query
345335
const queryBuilder = this.clickhouse.taskEventsV2.logsListQueryBuilder();
346336

347-
// PREWHERE for primary key columns (first in ORDER BY, uses index efficiently)
348337
queryBuilder.prewhere("environment_id = {environmentId: String}", {
349338
environmentId,
350339
});
351340

352-
// WHERE for non-indexed columns
353341
queryBuilder.where("organization_id = {organizationId: String}", {
354342
organizationId,
355343
});
@@ -358,7 +346,6 @@ export class LogsListPresenter {
358346
// Time filters - inserted_at in PREWHERE for partition pruning, start_time in WHERE
359347
if (effectiveFrom) {
360348
const fromNs = convertDateToNanoseconds(effectiveFrom).toString();
361-
// PREWHERE for partition key (inserted_at) - dramatically reduces data scanned
362349
queryBuilder.prewhere("inserted_at >= {insertedAtStart: DateTime64(3)}", {
363350
insertedAtStart: convertDateToClickhouseDateTime(effectiveFrom),
364351
});
@@ -370,7 +357,6 @@ export class LogsListPresenter {
370357
if (effectiveTo) {
371358
const clampedTo = effectiveTo > new Date() ? new Date() : effectiveTo;
372359
const toNs = convertDateToNanoseconds(clampedTo).toString();
373-
// PREWHERE for partition key (inserted_at) - dramatically reduces data scanned
374360
queryBuilder.prewhere("inserted_at <= {insertedAtEnd: DateTime64(3)}", {
375361
insertedAtEnd: convertDateToClickhouseDateTime(clampedTo),
376362
});
@@ -391,23 +377,22 @@ export class LogsListPresenter {
391377
queryBuilder.where("run_id IN {runIds: Array(String)}", { runIds });
392378
}
393379

394-
// Case-insensitive search in message and status fields
380+
// Case-insensitive search in message, attributes, and status fields
395381
if (search && search.trim() !== "") {
396382
const searchTerm = search.trim();
397383
queryBuilder.where(
398-
"(message ilike {searchPattern: String} OR status = {statusTerm: String})",
384+
"(message ilike {searchPattern: String} OR attributes_text ilike {searchPattern: String} OR status = {statusTerm: String})",
399385
{
400386
searchPattern: `%${searchTerm}%`,
401387
statusTerm: searchTerm.toUpperCase(),
402388
}
403389
);
404390
}
405391

406-
// Level filter (map display levels to ClickHouse kinds/statuses)
392+
407393
if (levels && levels.length > 0) {
408394
const conditions: string[] = [];
409-
const params: Record<string, unknown> = {};
410-
const hasTraceLevel = levels.includes("TRACE");
395+
const params: Record<string, any> = {};
411396
const hasErrorOrCancelledLevel = levels.includes("ERROR") || levels.includes("CANCELLED");
412397

413398
for (const level of levels) {
@@ -440,23 +425,17 @@ export class LogsListPresenter {
440425
}
441426

442427
if (conditions.length > 0) {
443-
queryBuilder.where(`(${conditions.join(" OR ")})`);
428+
queryBuilder.where(`(${conditions.join(" OR ")})`, params as any);
444429
}
445430
}
446431

447-
// Exclude DEBUG logs if not included
432+
// Debug logs are available only to admins
448433
if (includeDebugLogs === false) {
449434
queryBuilder.where("kind NOT IN {debugKinds: Array(String)}", {
450435
debugKinds: ["DEBUG_EVENT", "LOG_DEBUG"],
451436
});
452437
}
453438

454-
// Exclude TRACE logs with PARTIAL status
455-
// const traceKinds = ["SPAN", "ANCESTOR_OVERRIDE", "SPAN_EVENT"];
456-
// queryBuilder.where(
457-
// "NOT (kind IN {traceKinds: Array(String)} AND status = 'PARTIAL')",
458-
// { traceKinds }
459-
// );
460439
queryBuilder.where("NOT (kind = 'SPAN' AND status = 'PARTIAL')");
461440

462441

@@ -474,13 +453,11 @@ export class LogsListPresenter {
474453
);
475454
}
476455

477-
// Order by newest first
478456
queryBuilder.orderBy("start_time DESC, trace_id DESC, span_id DESC, run_id DESC");
479457

480458
// Limit + 1 to check if there are more results
481459
queryBuilder.limit(pageSize + 1);
482460

483-
// Execute query
484461
const [queryError, records] = await queryBuilder.execute();
485462

486463
if (queryError) {
@@ -505,20 +482,37 @@ export class LogsListPresenter {
505482

506483
// Transform results
507484
// Use :: as separator since dash conflicts with date format in start_time
508-
const transformedLogs = logs.map((log) => ({
509-
id: `${log.trace_id}::${log.span_id}::${log.run_id}::${log.start_time}`,
510-
runId: log.run_id,
511-
taskIdentifier: log.task_identifier,
512-
startTime: convertClickhouseDateTime64ToJsDate(log.start_time).toISOString(),
513-
traceId: log.trace_id,
514-
spanId: log.span_id,
515-
parentSpanId: log.parent_span_id || null,
516-
message: log.message,
517-
kind: log.kind,
518-
status: log.status,
519-
duration: typeof log.duration === "number" ? log.duration : Number(log.duration),
520-
level: kindToLevel(log.kind, log.status),
521-
}));
485+
const transformedLogs = logs.map((log) => {
486+
let displayMessage = log.message;
487+
488+
// For error logs with status ERROR, try to extract error message from attributes
489+
if (log.status === "ERROR" && log.attributes) {
490+
try {
491+
let attributes = log.attributes as Record<string, any>;
492+
493+
if (attributes?.error?.message && typeof attributes.error.message === 'string') {
494+
displayMessage = attributes.error.message;
495+
}
496+
} catch {
497+
// If attributes parsing fails, use the regular message
498+
}
499+
}
500+
501+
return {
502+
id: `${log.trace_id}::${log.span_id}::${log.run_id}::${log.start_time}`,
503+
runId: log.run_id,
504+
taskIdentifier: log.task_identifier,
505+
startTime: convertClickhouseDateTime64ToJsDate(log.start_time).toISOString(),
506+
traceId: log.trace_id,
507+
spanId: log.span_id,
508+
parentSpanId: log.parent_span_id || null,
509+
message: displayMessage,
510+
kind: log.kind,
511+
status: log.status,
512+
duration: typeof log.duration === "number" ? log.duration : Number(log.duration),
513+
level: kindToLevel(log.kind, log.status),
514+
};
515+
});
522516

523517
return {
524518
logs: transformedLogs,

internal-packages/clickhouse/src/taskEvents.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,8 @@ export const LogsListResult = z.object({
249249
kind: z.string(),
250250
status: z.string(),
251251
duration: z.number().or(z.string()),
252+
metadata: z.string(),
253+
attributes: z.any(),
252254
});
253255

254256
export type LogsListResult = z.output<typeof LogsListResult>;
@@ -271,6 +273,8 @@ export function getLogsListQueryBuilder(ch: ClickhouseReader, settings?: ClickHo
271273
"kind",
272274
"status",
273275
"duration",
276+
"metadata",
277+
"attributes"
274278
],
275279
settings: {
276280
max_memory_usage: "2000000000", // 2GB per query limit
@@ -297,7 +301,7 @@ export const LogDetailV2Result = z.object({
297301
status: z.string(),
298302
duration: z.number().or(z.string()),
299303
metadata: z.string(),
300-
attributes_text: z.string(),
304+
attributes: z.any()
301305
});
302306

303307
export type LogDetailV2Result = z.output<typeof LogDetailV2Result>;
@@ -321,7 +325,7 @@ export function getLogDetailQueryBuilder(ch: ClickhouseReader, settings?: ClickH
321325
"status",
322326
"duration",
323327
"metadata",
324-
"attributes_text",
328+
"attributes",
325329
],
326330
settings,
327331
});

0 commit comments

Comments
 (0)