mirror of
https://github.com/PostHog/posthog.git
synced 2024-11-24 09:14:46 +01:00
feat(queries): Make query runner add more tags to both query log and Sentry (#24254)
This commit is contained in:
parent
8233b4f5a0
commit
4aedc89447
Binary file not shown.
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 110 KiB |
Binary file not shown.
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 110 KiB |
@ -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<debugCHQueriesLogicType>([
|
||||
@ -59,7 +64,7 @@ const debugCHQueriesLogic = kea<debugCHQueriesLogicType>([
|
||||
[] 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 (
|
||||
<span className="font-mono whitespace-pre">
|
||||
{dayjs.tz(item.timestamp, 'UTC').tz().format().replace('T', '\n')}
|
||||
</span>
|
||||
<>
|
||||
<div className="font-mono whitespace-pre mb-2">
|
||||
{dayjs.tz(item.timestamp, 'UTC').tz().format().replace('T', '\n')}
|
||||
</div>
|
||||
<div>
|
||||
{item.status === 1 ? (
|
||||
'In progress…'
|
||||
) : (
|
||||
<>
|
||||
Took {Math.round((item.execution_time + Number.EPSILON) * 100) / 100} ms
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
},
|
||||
width: 160,
|
||||
@ -141,12 +157,71 @@ function DebugCHQueries(): JSX.Element {
|
||||
return (
|
||||
<div className="max-w-200 py-1 space-y-2">
|
||||
<div>
|
||||
<span className="font-bold tracking-wide">ID:</span>{' '}
|
||||
<span className="font-mono">{item.query_id}</span>
|
||||
<LemonTag className="inline-block">
|
||||
<span className="font-bold tracking-wide">ID:</span>{' '}
|
||||
<span className="font-mono">{item.query_id}</span>
|
||||
</LemonTag>{' '}
|
||||
{item.logComment.cache_key ? (
|
||||
<LemonTag className="inline-block">
|
||||
<span className="font-bold tracking-wide">Cache key:</span>{' '}
|
||||
<span className="font-mono">{item.logComment.cache_key}</span>{' '}
|
||||
<Link
|
||||
to={`https://sentry.io/issues/?query=is%3Aunresolved+cache_key%3A${item.logComment.cache_key}&referrer=issue-list&statsPeriod=7d`}
|
||||
className="inline-block"
|
||||
target="_blank"
|
||||
targetBlankIcon
|
||||
/>
|
||||
</LemonTag>
|
||||
) : null}{' '}
|
||||
{item.logComment.insight_id ? (
|
||||
<LemonTag className="inline-block">
|
||||
<span className="font-bold tracking-wide">Insight ID:</span>{' '}
|
||||
<span className="font-mono">{item.logComment.insight_id}</span>{' '}
|
||||
<Link
|
||||
to={`https://sentry.io/issues/?query=is%3Aunresolved+insight_id%3A${item.logComment.insight_id}&referrer=issue-list&statsPeriod=7d`}
|
||||
className="inline-block"
|
||||
target="_blank"
|
||||
targetBlankIcon
|
||||
/>
|
||||
</LemonTag>
|
||||
) : null}{' '}
|
||||
{item.logComment.dashboard_id ? (
|
||||
<LemonTag className="inline-block">
|
||||
<span className="font-bold tracking-wide">Dashboard ID:</span>{' '}
|
||||
<span className="font-mono">{item.logComment.dashboard_id}</span>{' '}
|
||||
<Link
|
||||
to={`https://sentry.io/issues/?query=is%3Aunresolved+dashboard_id%3A${item.logComment.dashboard_id}&referrer=issue-list&statsPeriod=7d`}
|
||||
className="inline-block"
|
||||
target="_blank"
|
||||
targetBlankIcon
|
||||
/>
|
||||
</LemonTag>
|
||||
) : null}{' '}
|
||||
{item.logComment.user_id ? (
|
||||
<LemonTag className="inline-block">
|
||||
<span className="font-bold tracking-wide">User ID:</span>{' '}
|
||||
<span className="font-mono">{item.logComment.user_id}</span>{' '}
|
||||
<Link
|
||||
to={`https://sentry.io/issues/?query=is%3Aunresolved+user%3A%22id%3A${item.logComment.user_id}%22&referrer=issue-list&statsPeriod=7d`}
|
||||
className="inline-block"
|
||||
target="_blank"
|
||||
targetBlankIcon
|
||||
/>
|
||||
</LemonTag>
|
||||
) : null}
|
||||
</div>
|
||||
{item.exception && (
|
||||
<LemonBanner type="error" className="text-xs font-mono">
|
||||
{item.exception}
|
||||
<div>{item.exception}</div>
|
||||
<LemonButton
|
||||
type="secondary"
|
||||
size="xsmall"
|
||||
to={`https://sentry.io/issues/?query=is%3Aunresolved+issue.priority%3A%5Bhigh%2C+medium%5D+trace%3A${item.logComment.sentry_trace}&statsPeriod=1d`}
|
||||
targetBlank
|
||||
className="mt-4 mb-1"
|
||||
>
|
||||
View in Sentry
|
||||
</LemonButton>
|
||||
</LemonBanner>
|
||||
)}
|
||||
<CodeSnippet
|
||||
@ -157,40 +232,33 @@ function DebugCHQueries(): JSX.Element {
|
||||
>
|
||||
{item.query}
|
||||
</CodeSnippet>
|
||||
{item.queryJson ? (
|
||||
{item.logComment.query ? (
|
||||
<LemonButton
|
||||
type="primary"
|
||||
size="small"
|
||||
fullWidth
|
||||
center
|
||||
icon={<IconCodeInsert />}
|
||||
to={urls.debugQuery(item.queryJson)}
|
||||
to={urls.debugQuery(item.logComment.query)}
|
||||
targetBlank
|
||||
sideAction={{
|
||||
icon: <IconCopy />,
|
||||
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
|
||||
</LemonButton>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
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"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
@ -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
|
||||
]
|
||||
}
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user