Skip to content

Commit 78b3493

Browse files
committed
removed EVENT_REPOSITORY_CLICKHOUSE_ROLLOUT_PERCENT
added support for tasks_v1 for logs cleaned the No logs message extended LogsPresenter with BasePresenter for spans
1 parent 4dc504d commit 78b3493

File tree

9 files changed

+233
-97
lines changed

9 files changed

+233
-97
lines changed

apps/webapp/app/components/logs/LogsTable.tsx

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import { PopoverMenuItem } from "~/components/primitives/Popover";
2727

2828
type LogsTableProps = {
2929
logs: LogEntry[];
30-
hasFilters: boolean;
3130
searchTerm?: string;
3231
isLoading?: boolean;
3332
isLoadingMore?: boolean;
@@ -59,7 +58,6 @@ function getLevelBorderColor(level: LogEntry["level"]): string {
5958

6059
export function LogsTable({
6160
logs,
62-
hasFilters,
6361
searchTerm,
6462
isLoading = false,
6563
isLoadingMore = false,
@@ -126,11 +124,7 @@ export function LogsTable({
126124
</TableRow>
127125
</TableHeader>
128126
<TableBody>
129-
{logs.length === 0 && !hasFilters ? (
130-
<TableBlankRow colSpan={6}>
131-
{!isLoading && <NoLogs title="No logs found" />}
132-
</TableBlankRow>
133-
) : logs.length === 0 ? (
127+
{logs.length === 0 ? (
134128
<BlankState isLoading={isLoading} onRefresh={() => window.location.reload()} />
135129
) : (
136130
logs.map((log) => {
@@ -214,14 +208,6 @@ export function LogsTable({
214208
);
215209
}
216210

217-
function NoLogs({ title }: { title: string }) {
218-
return (
219-
<div className="flex items-center justify-center">
220-
<Paragraph className="w-auto">{title}</Paragraph>
221-
</div>
222-
);
223-
}
224-
225211
function BlankState({ isLoading, onRefresh }: { isLoading?: boolean; onRefresh?: () => void }) {
226212
if (isLoading) return <TableBlankRow colSpan={6}></TableBlankRow>;
227213

apps/webapp/app/env.server.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1220,7 +1220,6 @@ const EnvironmentSchema = z
12201220
.number()
12211221
.int()
12221222
.default(60_000 * 5), // 5 minutes
1223-
EVENT_REPOSITORY_CLICKHOUSE_ROLLOUT_PERCENT: z.coerce.number().optional(),
12241223
EVENT_REPOSITORY_DEFAULT_STORE: z
12251224
.enum(["postgres", "clickhouse", "clickhouse_v2"])
12261225
.default("postgres"),

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { type ClickHouse } from "@internal/clickhouse";
22
import { type PrismaClientOrTransaction } from "@trigger.dev/database";
33
import { convertClickhouseDateTime64ToJsDate } from "~/v3/eventRepository/clickhouseEventRepository.server";
44
import { kindToLevel } from "~/utils/logUtils";
5+
import { getConfiguredEventRepository } from "~/v3/eventRepository/index.server";
6+
import { ServiceValidationError } from "~/v3/services/baseService.server";
57

68
export type LogDetailOptions = {
79
environmentId: string;
@@ -24,8 +26,28 @@ export class LogDetailPresenter {
2426
public async call(options: LogDetailOptions) {
2527
const { environmentId, organizationId, projectId, spanId, traceId, startTime } = options;
2628

27-
// Build ClickHouse query
28-
const queryBuilder = this.clickhouse.taskEventsV2.logDetailQueryBuilder();
29+
// Determine which store to use based on organization configuration
30+
const { store } = await getConfiguredEventRepository(organizationId);
31+
32+
// Throw error if postgres is detected
33+
if (store === "postgres") {
34+
throw new ServiceValidationError(
35+
"Log details are not available for PostgreSQL event store. Please contact support."
36+
);
37+
}
38+
39+
// Throw error if clickhouse v1 is detected (not supported)
40+
if (store === "postgres") {
41+
throw new ServiceValidationError(
42+
"Log details are not available for postgres event store. Please contact support."
43+
);
44+
}
45+
46+
// Build ClickHouse query - only v2 is supported for log details
47+
const isClickhouseV2 = store === "clickhouse_v2";
48+
const queryBuilder = isClickhouseV2
49+
? this.clickhouse.taskEventsV2.logDetailQueryBuilder()
50+
: this.clickhouse.taskEvents.logDetailQueryBuilder();
2951

3052
// Required filters - spanId, traceId, and startTime uniquely identify the log
3153
// Multiple events can share the same spanId (span, span events, logs), so startTime is needed

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

Lines changed: 70 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
TaskRunStatus as TaskRunStatusEnum,
99
TaskTriggerSource,
1010
} from "@trigger.dev/database";
11+
import { getConfiguredEventRepository } from "~/v3/eventRepository/index.server";
1112

1213
// Create a schema that validates TaskRunStatus enum values
1314
const TaskRunStatusSchema = z.array(z.nativeEnum(TaskRunStatusEnum));
@@ -23,6 +24,8 @@ import {
2324
convertClickhouseDateTime64ToJsDate,
2425
} from "~/v3/eventRepository/clickhouseEventRepository.server";
2526
import { kindToLevel, type LogLevel, LogLevelSchema } from "~/utils/logUtils";
27+
import { BasePresenter } from "~/presenters/v3/basePresenter.server";
28+
2629

2730
export type { LogLevel };
2831

@@ -131,9 +134,7 @@ function decodeCursor(cursor: string): LogCursor | null {
131134
}
132135

133136
// Convert display level to ClickHouse kinds and statuses
134-
function levelToKindsAndStatuses(
135-
level: LogLevel
136-
): { kinds?: string[]; statuses?: string[] } {
137+
function levelToKindsAndStatuses(level: LogLevel): { kinds?: string[]; statuses?: string[] } {
137138
switch (level) {
138139
case "DEBUG":
139140
return { kinds: ["DEBUG_EVENT", "LOG_DEBUG"] };
@@ -150,7 +151,6 @@ function levelToKindsAndStatuses(
150151
}
151152
}
152153

153-
154154
function convertDateToNanoseconds(date: Date): bigint {
155155
return BigInt(date.getTime()) * 1_000_000n;
156156
}
@@ -168,11 +168,13 @@ function formatNanosecondsForClickhouse(ns: bigint): string {
168168
return padded.slice(0, 10) + "." + padded.slice(10);
169169
}
170170

171-
export class LogsListPresenter {
171+
export class LogsListPresenter extends BasePresenter {
172172
constructor(
173173
private readonly replica: PrismaClientOrTransaction,
174174
private readonly clickhouse: ClickHouse
175-
) {}
175+
) {
176+
super();
177+
}
176178

177179
public async call(
178180
organizationId: string,
@@ -242,10 +244,7 @@ export class LogsListPresenter {
242244
(search !== undefined && search !== "") ||
243245
!time.isDefault;
244246

245-
const possibleTasksAsync = getAllTaskIdentifiers(
246-
this.replica,
247-
environmentId
248-
);
247+
const possibleTasksAsync = getAllTaskIdentifiers(this.replica, environmentId);
249248

250249
const bulkActionsAsync = this.replica.bulkActionGroup.findMany({
251250
select: {
@@ -264,31 +263,26 @@ export class LogsListPresenter {
264263
take: 20,
265264
});
266265

267-
const [possibleTasks, bulkActions, displayableEnvironment] =
268-
await Promise.all([
269-
possibleTasksAsync,
270-
bulkActionsAsync,
271-
findDisplayableEnvironment(environmentId, userId),
272-
]);
273-
274-
if (
275-
bulkId &&
276-
!bulkActions.some((bulkAction) => bulkAction.friendlyId === bulkId)
277-
) {
278-
const selectedBulkAction =
279-
await this.replica.bulkActionGroup.findFirst({
280-
select: {
281-
friendlyId: true,
282-
type: true,
283-
createdAt: true,
284-
name: true,
285-
},
286-
where: {
287-
friendlyId: bulkId,
288-
projectId,
289-
environmentId,
290-
},
291-
});
266+
const [possibleTasks, bulkActions, displayableEnvironment] = await Promise.all([
267+
possibleTasksAsync,
268+
bulkActionsAsync,
269+
findDisplayableEnvironment(environmentId, userId),
270+
]);
271+
272+
if (bulkId && !bulkActions.some((bulkAction) => bulkAction.friendlyId === bulkId)) {
273+
const selectedBulkAction = await this.replica.bulkActionGroup.findFirst({
274+
select: {
275+
friendlyId: true,
276+
type: true,
277+
createdAt: true,
278+
name: true,
279+
},
280+
where: {
281+
friendlyId: bulkId,
282+
projectId,
283+
environmentId,
284+
},
285+
});
292286

293287
if (selectedBulkAction) {
294288
bulkActions.push(selectedBulkAction);
@@ -371,7 +365,29 @@ export class LogsListPresenter {
371365
}
372366
}
373367

374-
const queryBuilder = this.clickhouse.taskEventsV2.logsListQueryBuilder();
368+
// Determine which store to use based on organization configuration
369+
const { store } = await getConfiguredEventRepository(organizationId);
370+
371+
// Throw error if postgres is detected
372+
if (store === "postgres") {
373+
throw new ServiceValidationError(
374+
"Logs are not available for PostgreSQL event store. Please contact support."
375+
);
376+
}
377+
378+
// Throw error if clickhouse v1 is detected (not supported)
379+
if (store === "postgres") {
380+
throw new ServiceValidationError(
381+
"Logs are not available for Postgres event store. Please contact support."
382+
);
383+
}
384+
385+
// Get the appropriate query builder based on store type
386+
const isClickhouseV2 = store === "clickhouse_v2";
387+
388+
const queryBuilder = isClickhouseV2
389+
? this.clickhouse.taskEventsV2.logsListQueryBuilder()
390+
: this.clickhouse.taskEvents.logsListQueryBuilder();
375391

376392
queryBuilder.prewhere("environment_id = {environmentId: String}", {
377393
environmentId,
@@ -382,12 +398,17 @@ export class LogsListPresenter {
382398
});
383399
queryBuilder.where("project_id = {projectId: String}", { projectId });
384400

385-
// Time filters - inserted_at in PREWHERE for partition pruning, start_time in WHERE
401+
// Time filters - inserted_at in PREWHERE only for v2, start_time in WHERE for both
386402
if (effectiveFrom) {
387403
const fromNs = convertDateToNanoseconds(effectiveFrom);
388-
queryBuilder.prewhere("inserted_at >= {insertedAtStart: DateTime64(3)}", {
389-
insertedAtStart: convertDateToClickhouseDateTime(effectiveFrom),
390-
});
404+
405+
// Only use inserted_at for partition pruning if v2
406+
if (isClickhouseV2) {
407+
queryBuilder.prewhere("inserted_at >= {insertedAtStart: DateTime64(3)}", {
408+
insertedAtStart: convertDateToClickhouseDateTime(effectiveFrom),
409+
});
410+
}
411+
391412
queryBuilder.where("start_time >= {fromTime: String}", {
392413
fromTime: formatNanosecondsForClickhouse(fromNs),
393414
});
@@ -396,9 +417,14 @@ export class LogsListPresenter {
396417
if (effectiveTo) {
397418
const clampedTo = effectiveTo > new Date() ? new Date() : effectiveTo;
398419
const toNs = convertDateToNanoseconds(clampedTo);
399-
queryBuilder.prewhere("inserted_at <= {insertedAtEnd: DateTime64(3)}", {
400-
insertedAtEnd: convertDateToClickhouseDateTime(clampedTo),
401-
});
420+
421+
// Only use inserted_at for partition pruning if v2
422+
if (isClickhouseV2) {
423+
queryBuilder.prewhere("inserted_at <= {insertedAtEnd: DateTime64(3)}", {
424+
insertedAtEnd: convertDateToClickhouseDateTime(clampedTo),
425+
});
426+
}
427+
402428
queryBuilder.where("start_time <= {toTime: String}", {
403429
toTime: formatNanosecondsForClickhouse(toNs),
404430
});
@@ -428,7 +454,6 @@ export class LogsListPresenter {
428454
);
429455
}
430456

431-
432457
if (levels && levels.length > 0) {
433458
const conditions: string[] = [];
434459
const params: Record<string, string[]> = {};
@@ -477,7 +502,6 @@ export class LogsListPresenter {
477502

478503
queryBuilder.where("NOT (kind = 'SPAN' AND status = 'PARTIAL')");
479504

480-
481505
// Cursor pagination
482506
const decodedCursor = cursor ? decodeCursor(cursor) : null;
483507
if (decodedCursor) {
@@ -529,7 +553,7 @@ export class LogsListPresenter {
529553
try {
530554
let attributes = log.attributes as ErrorAttributes;
531555

532-
if (attributes?.error?.message && typeof attributes.error.message === 'string') {
556+
if (attributes?.error?.message && typeof attributes.error.message === "string") {
533557
displayMessage = attributes.error.message;
534558
}
535559
} catch {

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.logs/route.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { type LoaderFunctionArgs , redirect} from "@remix-run/server-runtime";
22
import { type MetaFunction, useFetcher, useNavigation, useLocation } from "@remix-run/react";
3+
import { ServiceValidationError } from "~/v3/services/baseService.server";
34
import {
45
TypedAwait,
56
typeddefer,
@@ -86,22 +87,28 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
8687
const showDebug = url.searchParams.get("showDebug") === "true";
8788

8889
const presenter = new LogsListPresenter($replica, clickhouseClient);
89-
const list = presenter.call(project.organizationId, environment.id, {
90+
91+
const listPromise = presenter.call(project.organizationId, environment.id, {
9092
userId,
9193
projectId: project.id,
9294
...filters,
9395
search,
9496
levels,
9597
includeDebugLogs: isAdmin && showDebug,
9698
defaultPeriod: "1h",
99+
}).catch((error) => {
100+
if (error instanceof ServiceValidationError) {
101+
return { error: error.message };
102+
}
103+
throw error;
97104
});
98105

99106
const session = await setRootOnlyFilterPreference(filters.rootOnly, request);
100107
const cookieValue = await uiPreferencesStorage.commitSession(session);
101108

102109
return typeddefer(
103110
{
104-
data: list,
111+
data: listPromise,
105112
rootOnlyDefault: filters.rootOnly,
106113
filters,
107114
isAdmin,
@@ -149,10 +156,20 @@ export default function Page() {
149156
</div>
150157
}
151158
>
152-
{(list) => {
159+
{(result) => {
160+
// Check if result contains an error
161+
if ("error" in result) {
162+
return (
163+
<div className="flex items-center justify-center px-3 py-12">
164+
<Callout variant="error" className="max-w-fit">
165+
{result.error}
166+
</Callout>
167+
</div>
168+
);
169+
}
153170
return (
154171
<LogsList
155-
list={list}
172+
list={result}
156173
rootOnlyDefault={rootOnlyDefault}
157174
isAdmin={isAdmin}
158175
showDebug={showDebug}
@@ -307,7 +324,6 @@ function LogsList({
307324
{/* Table */}
308325
<LogsTable
309326
logs={accumulatedLogs}
310-
hasFilters={list.hasFilters}
311327
searchTerm={list.searchTerm}
312328
isLoading={isLoading}
313329
isLoadingMore={fetcher.state === "loading"}

0 commit comments

Comments
 (0)