diff --git a/frontend/__snapshots__/exporter-exporter--user-paths-insight--dark.png b/frontend/__snapshots__/exporter-exporter--user-paths-insight--dark.png index ddabab3e270..acc74d32b13 100644 Binary files a/frontend/__snapshots__/exporter-exporter--user-paths-insight--dark.png and b/frontend/__snapshots__/exporter-exporter--user-paths-insight--dark.png differ diff --git a/frontend/__snapshots__/exporter-exporter--user-paths-insight--light.png b/frontend/__snapshots__/exporter-exporter--user-paths-insight--light.png index 2b0e70c4b49..7a1a6ee0a0a 100644 Binary files a/frontend/__snapshots__/exporter-exporter--user-paths-insight--light.png and b/frontend/__snapshots__/exporter-exporter--user-paths-insight--light.png differ diff --git a/frontend/src/lib/components/CommandPalette/DebugCHQueries.tsx b/frontend/src/lib/components/CommandPalette/DebugCHQueries.tsx index 16507a4890b..7d51c495986 100644 --- a/frontend/src/lib/components/CommandPalette/DebugCHQueries.tsx +++ b/frontend/src/lib/components/CommandPalette/DebugCHQueries.tsx @@ -8,6 +8,8 @@ import { LemonBanner } from 'lib/lemon-ui/LemonBanner' import { LemonButton } from 'lib/lemon-ui/LemonButton' import { LemonDialog } from 'lib/lemon-ui/LemonDialog' import { LemonTable } from 'lib/lemon-ui/LemonTable' +import { LemonTag } from 'lib/lemon-ui/LemonTag' +import { Link } from 'lib/lemon-ui/Link' import { humanizeBytes } from 'lib/utils' import { copyToClipboard } from 'lib/utils/copyToClipboard' import { useState } from 'react' @@ -30,7 +32,6 @@ export interface Query { timestamp: string query: string query_id: string - queryJson: string exception: string /** * 1 means running, 2 means finished, 3 means errored before execution, 4 means errored during execution. @@ -39,6 +40,10 @@ export interface Query { status: 1 | 2 | 3 | 4 execution_time: number path: string + logComment: { + query: any + [key: string]: any + } } const debugCHQueriesLogic = kea([ @@ -59,7 +64,7 @@ const debugCHQueriesLogic = kea([ [] as Query[], { loadQueries: async () => { - return await api.get('api/debug_ch_queries/') + return (await api.get('api/debug_ch_queries/')).queries }, }, ], @@ -128,9 +133,20 @@ function DebugCHQueries(): JSX.Element { title: 'Timestamp', render: function Timestamp(_, item) { return ( - - {dayjs.tz(item.timestamp, 'UTC').tz().format().replace('T', '\n')} - + <> +
+ {dayjs.tz(item.timestamp, 'UTC').tz().format().replace('T', '\n')} +
+
+ {item.status === 1 ? ( + 'In progress…' + ) : ( + <> + Took {Math.round((item.execution_time + Number.EPSILON) * 100) / 100} ms + + )} +
+ ) }, width: 160, @@ -141,12 +157,71 @@ function DebugCHQueries(): JSX.Element { return (
- ID:{' '} - {item.query_id} + + ID:{' '} + {item.query_id} + {' '} + {item.logComment.cache_key ? ( + + Cache key:{' '} + {item.logComment.cache_key}{' '} + + + ) : null}{' '} + {item.logComment.insight_id ? ( + + Insight ID:{' '} + {item.logComment.insight_id}{' '} + + + ) : null}{' '} + {item.logComment.dashboard_id ? ( + + Dashboard ID:{' '} + {item.logComment.dashboard_id}{' '} + + + ) : null}{' '} + {item.logComment.user_id ? ( + + User ID:{' '} + {item.logComment.user_id}{' '} + + + ) : null}
{item.exception && ( - {item.exception} +
{item.exception}
+ + View in Sentry +
)} {item.query} - {item.queryJson ? ( + {item.logComment.query ? ( } - to={urls.debugQuery(item.queryJson)} + to={urls.debugQuery(item.logComment.query)} targetBlank sideAction={{ icon: , - onClick: () => void copyToClipboard(item.queryJson, 'query JSON'), + onClick: () => + void copyToClipboard( + JSON.stringify(item.logComment.query), + 'query JSON' + ), tooltip: 'Copy query JSON to clipboard', }} className="my-0" > - Debug {JSON.parse(item.queryJson).kind || 'query'} in new tab + Debug {item.logComment.query.kind || 'query'} in new tab ) : null}
) }, }, - - { - title: 'Duration', - render: function Duration(_, item) { - if (item.status === 1) { - return 'In progress…' - } - return <>{Math.round((item.execution_time + Number.EPSILON) * 100) / 100} ms - }, - align: 'right', - }, { title: 'Profiling stats', render: function ProfilingStats(_, item) { @@ -287,6 +355,7 @@ function DebugCHQueries(): JSX.Element { loading={queriesLoading} loadingSkeletonRows={5} pagination={undefined} + rowClassName="align-top" /> ) diff --git a/posthog/api/debug_ch_queries.py b/posthog/api/debug_ch_queries.py index 5e158915677..fd323e2f95f 100644 --- a/posthog/api/debug_ch_queries.py +++ b/posthog/api/debug_ch_queries.py @@ -1,3 +1,4 @@ +import json import re from typing import Optional @@ -33,17 +34,16 @@ class DebugCHQueries(viewsets.ViewSet): SELECT query_id, argMax(query, type) AS query, - argMax(query_json, type) AS query_json, argMax(query_start_time, type) AS query_start_time, argMax(exception, type) AS exception, argMax(query_duration_ms, type) AS query_duration_ms, argMax(ProfileEvents, type) as profile_events, + argMax(log_comment, type) AS log_comment, max(type) AS status FROM ( SELECT query_id, query, query_start_time, exception, query_duration_ms, toInt8(type) AS type, - JSONExtractRaw(log_comment, 'query') as query_json, - ProfileEvents + ProfileEvents, log_comment FROM clusterAllReplicas(%(cluster)s, system, query_log) WHERE query LIKE %(query)s AND @@ -63,18 +63,20 @@ class DebugCHQueries(viewsets.ViewSet): }, ) return Response( - [ - { - "query_id": resp[0], - "query": resp[1], - "queryJson": resp[2], - "timestamp": resp[3], - "exception": resp[4], - "execution_time": resp[5], - "profile_events": resp[6], - "status": resp[7], - "path": self._get_path(resp[1]), - } - for resp in response - ] + { + "queries": [ + { + "query_id": resp[0], + "query": resp[1], + "timestamp": resp[2], + "exception": resp[3], + "execution_time": resp[4], + "profile_events": resp[5], + "logComment": json.loads(resp[6]), + "status": resp[7], + "path": self._get_path(resp[1]), + } + for resp in response + ] + } ) diff --git a/posthog/api/query.py b/posthog/api/query.py index f51ae9b3cbb..acda400e51b 100644 --- a/posthog/api/query.py +++ b/posthog/api/query.py @@ -4,14 +4,13 @@ import uuid from django.http import JsonResponse from drf_spectacular.utils import OpenApiResponse from pydantic import BaseModel -from posthog.hogql_queries.query_runner import ExecutionMode, execution_mode_from_refresh +from rest_framework import status from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.exceptions import ValidationError, NotAuthenticated from rest_framework.request import Request from rest_framework.response import Response -from sentry_sdk import capture_exception -from rest_framework import status +from sentry_sdk import capture_exception, set_tag from posthog.api.documentation import extend_schema from posthog.api.mixins import PydanticModelMixin @@ -25,6 +24,7 @@ from posthog.clickhouse.query_tagging import tag_queries from posthog.errors import ExposedCHQueryError from posthog.hogql.ai import PromptUnclear, write_sql_from_prompt from posthog.hogql.errors import ExposedHogQLError +from posthog.hogql_queries.query_runner import ExecutionMode, execution_mode_from_refresh from posthog.models.user import User from posthog.rate_limit import ( AIBurstRateThrottle, @@ -159,3 +159,4 @@ class QueryViewSet(TeamAndOrgViewSetMixin, PydanticModelMixin, viewsets.ViewSet) return tag_queries(client_query_id=query_id) + set_tag("client_query_id", query_id) diff --git a/posthog/hogql_queries/query_runner.py b/posthog/hogql_queries/query_runner.py index 4bb0832acff..faa75c20b12 100644 --- a/posthog/hogql_queries/query_runner.py +++ b/posthog/hogql_queries/query_runner.py @@ -6,7 +6,7 @@ from typing import Any, Generic, Optional, TypeVar, Union, cast, TypeGuard import structlog from prometheus_client import Counter from pydantic import BaseModel, ConfigDict -from sentry_sdk import capture_exception, push_scope +from sentry_sdk import capture_exception, push_scope, set_tag, get_traceparent from posthog.caching.utils import is_stale, ThresholdMode, cache_target_age, last_refresh_from_cached_result from posthog.clickhouse.client.execute_async import enqueue_process_query_task, get_query_status, QueryNotFoundError @@ -538,7 +538,17 @@ class QueryRunner(ABC, Generic[Q, R, CR]): dashboard_id: Optional[int] = None, ) -> CR | CacheMissResponse | QueryStatusResponse: cache_key = self.get_cache_key() + tag_queries(cache_key=cache_key) + tag_queries(sentry_trace=get_traceparent()) + set_tag("cache_key", cache_key) + if insight_id: + tag_queries(insight_id=insight_id) + set_tag("insight_id", str(insight_id)) + if dashboard_id: + tag_queries(dashboard_id=dashboard_id) + set_tag("dashboard_id", str(dashboard_id)) + self.query_id = query_id or self.query_id CachedResponse: type[CR] = self.cached_response_type cache_manager = QueryCacheManager(