0
0
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:
Julian Bez 2024-08-08 15:53:40 +01:00 committed by GitHub
parent 8233b4f5a0
commit 4aedc89447
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 126 additions and 44 deletions

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

View File

@ -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"
/>
</>
)

View File

@ -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
]
}
)

View File

@ -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)

View File

@ -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(