Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
256 changes: 149 additions & 107 deletions static/app/views/explore/logs/logsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {Fragment, memo, useCallback, useEffect, useMemo, useRef, useState} from 'react';

import {Button} from '@sentry/scraps/button';
import {TabList, Tabs} from '@sentry/scraps/tabs';
Expand All @@ -21,6 +21,7 @@ import {t} from 'sentry/locale';
import {LogsAnalyticsPageSource} from 'sentry/utils/analytics/logsAnalyticsEvent';
import {DiscoverDatasets} from 'sentry/utils/discover/types';
import {parsePeriodToHours} from 'sentry/utils/duration/parsePeriodToHours';
import type {AggregationKey} from 'sentry/utils/fields';
import {HOUR} from 'sentry/utils/formatters';
import {useQueryClient, type InfiniteData} from 'sentry/utils/queryClient';
import {useChartInterval} from 'sentry/utils/useChartInterval';
Expand Down Expand Up @@ -112,13 +113,136 @@ function LogsSearchBar({tracesItemSearchQueryBuilderProps}: LogsSearchBarProps)
return <TraceItemSearchQueryBuilder {...tracesItemSearchQueryBuilderProps} />;
}

export function LogsTabContent({datePageFilterProps}: LogsTabProps) {
interface LogsSearchSectionProps {
datePageFilterProps: DatePageFilterProps;
searchBarWidthOffset?: number;
}

const LogsSearchSection = memo(function LogsSearchSection({
datePageFilterProps,
searchBarWidthOffset,
}: LogsSearchSectionProps) {
const organization = useOrganization();
const pageFilters = usePageFilters();
const logsSearch = useQueryParamsSearch();
const fields = useQueryParamsFields();
const logsSearchQuery = logsSearch.formatString();
const groupBys = useQueryParamsGroupBys();
const mode = useQueryParamsMode();
const [interval] = useChartInterval();
const visualizes = useQueryParamsVisualizes();
const aggregateSortBys = useQueryParamsAggregateSortBys();

// AI search is gated behind the gen-ai-search-agent-translate feature flag
const areAiFeaturesAllowed =
!organization?.hideAiFeatures &&
organization.features.includes('gen-ai-features') &&
organization.features.includes('gen-ai-search-agent-translate');

const saveAsItems = useSaveAsItems({
visualizes,
groupBys,
interval,
mode,
search: logsSearch,
sortBys: aggregateSortBys,
});

const {
attributes: stringAttributes,
isLoading: stringAttributesLoading,
secondaryAliases: stringSecondaryAliases,
} = useLogItemAttributes({}, 'string', HiddenLogSearchFields);
const {
attributes: numberAttributes,
isLoading: numberAttributesLoading,
secondaryAliases: numberSecondaryAliases,
} = useLogItemAttributes({}, 'number', HiddenLogSearchFields);
const {
attributes: booleanAttributes,
isLoading: booleanAttributesLoading,
secondaryAliases: booleanSecondaryAliases,
} = useLogItemAttributes({}, 'boolean', HiddenLogSearchFields);

const {tracesItemSearchQueryBuilderProps, searchQueryBuilderProviderProps} =
useLogsSearchQueryBuilderProps({
booleanAttributes,
numberAttributes,
stringAttributes,
booleanSecondaryAliases,
numberSecondaryAliases,
stringSecondaryAliases,
});

const supportedAggregates = useMemo<AggregationKey[]>(() => {
return [];
}, []);

return (
<SearchQueryBuilderProvider
enableAISearch={areAiFeaturesAllowed}
aiSearchBadgeType="alpha"
{...searchQueryBuilderProviderProps}
>
<ExploreBodySearch>
<Layout.Main width="full">
<LogsFilterSection>
<StyledPageFilterBar condensed>
<ProjectPageFilter />
<EnvironmentPageFilter />
<DatePageFilter
{...datePageFilterProps}
searchPlaceholder={t('Custom range: 2h, 4d, 3w')}
/>
</StyledPageFilterBar>
<LogsSearchBar
tracesItemSearchQueryBuilderProps={tracesItemSearchQueryBuilderProps}
/>
{saveAsItems.length > 0 && (
<DropdownMenu
items={saveAsItems}
trigger={triggerProps => (
<Button
{...triggerProps}
priority="default"
aria-label={t('Save as')}
onClick={e => {
e.stopPropagation();
e.preventDefault();

triggerProps.onClick?.(e);
}}
>
{t('Save as')}
</Button>
)}
/>
)}
</LogsFilterSection>
<ExploreSchemaHintsSection>
<SchemaHintsList
supportedAggregates={supportedAggregates}
booleanTags={booleanAttributes}
numberTags={numberAttributes}
stringTags={stringAttributes}
isLoading={
numberAttributesLoading ||
stringAttributesLoading ||
booleanAttributesLoading
}
exploreQuery={logsSearchQuery}
source={SchemaHintsSources.LOGS}
searchBarWidthOffset={searchBarWidthOffset}
/>
</ExploreSchemaHintsSection>
</Layout.Main>
</ExploreBodySearch>
</SearchQueryBuilderProvider>
);
});

export function LogsTabContent({datePageFilterProps}: LogsTabProps) {
const pageFilters = usePageFilters();
const fields = useQueryParamsFields();
const mode = useQueryParamsMode();
const topEventsLimit = useQueryParamsTopEventsLimit();
const queryClient = useQueryClient();
const sortBys = useQueryParamsSortBys();
Expand All @@ -128,11 +252,6 @@ export function LogsTabContent({datePageFilterProps}: LogsTabProps) {
const tableData = useLogsPageDataQueryResult();
const autorefreshEnabled = useLogsAutoRefreshEnabled();

// AI search is gated behind the gen-ai-search-agent-translate feature flag
const areAiFeaturesAllowed =
!organization?.hideAiFeatures &&
organization.features.includes('gen-ai-features') &&
organization.features.includes('gen-ai-search-agent-translate');
const [timeseriesIngestDelay, setTimeseriesIngestDelay] = useState<bigint>(
getMaxIngestDelayTimestamp()
);
Expand Down Expand Up @@ -169,21 +288,21 @@ export function LogsTabContent({datePageFilterProps}: LogsTabProps) {
limit: 50,
});

const {
attributes: stringAttributes,
isLoading: stringAttributesLoading,
secondaryAliases: stringSecondaryAliases,
} = useLogItemAttributes({}, 'string', HiddenLogSearchFields);
const {
attributes: numberAttributes,
isLoading: numberAttributesLoading,
secondaryAliases: numberSecondaryAliases,
} = useLogItemAttributes({}, 'number', HiddenLogSearchFields);
const {
attributes: booleanAttributes,
isLoading: booleanAttributesLoading,
secondaryAliases: booleanSecondaryAliases,
} = useLogItemAttributes({}, 'boolean', HiddenLogSearchFields);
const {attributes: stringAttributes} = useLogItemAttributes(
{},
'string',
HiddenLogSearchFields
);
const {attributes: numberAttributes} = useLogItemAttributes(
{},
'number',
HiddenLogSearchFields
);
const {attributes: booleanAttributes} = useLogItemAttributes(
{},
'boolean',
HiddenLogSearchFields
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate attribute hooks double computation in performance PR

Low Severity

useLogItemAttributes is called three times each in both LogsSearchSection and LogsTabContent, totaling six calls. Each call internally runs useTraceItemAttributeConfig, which fetches and computes all three attribute types (string, number, boolean) regardless of the type argument — so the underlying computation runs twice. In a PR focused on reducing unnecessary work during auto-refresh, this duplication is counterproductive. The attributes could be fetched once in LogsTabContent and passed as props to LogsSearchSection, which would also reduce the number of hooks that can trigger re-renders inside the memoized component.

Additional Locations (1)
Fix in Cursor Fix in Web

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will look into in a follow up.


const averageLogsPerSecond = calculateAverageLogsPerSecond(timeseriesResult);

Expand All @@ -200,20 +319,6 @@ export function LogsTabContent({datePageFilterProps}: LogsTabProps) {
aggregateSortBys,
});

const {tracesItemSearchQueryBuilderProps, searchQueryBuilderProviderProps} =
useLogsSearchQueryBuilderProps({
booleanAttributes,
numberAttributes,
stringAttributes,
booleanSecondaryAliases,
numberSecondaryAliases,
stringSecondaryAliases,
});

const supportedAggregates = useMemo(() => {
return [];
}, []);

const refreshTable = useCallback(async () => {
setTimeseriesIngestDelay(getMaxIngestDelayTimestamp());
queryClient.setQueryData(
Expand Down Expand Up @@ -277,15 +382,6 @@ export function LogsTabContent({datePageFilterProps}: LogsTabProps) {
[setSidebarOpen, setMode]
);

const saveAsItems = useSaveAsItems({
visualizes,
groupBys,
interval,
mode,
search: logsSearch,
sortBys: aggregateSortBys,
});

/**
* Manual refresh doesn't work for longer relative periods as it hits cacheing. Only allow manual refresh if the relative period or absolute time range is less than 1 hour.
*/
Expand Down Expand Up @@ -330,65 +426,11 @@ export function LogsTabContent({datePageFilterProps}: LogsTabProps) {
const {infiniteLogsQueryResult} = useLogsPageData();

return (
<SearchQueryBuilderProvider
enableAISearch={areAiFeaturesAllowed}
aiSearchBadgeType="alpha"
{...searchQueryBuilderProviderProps}
>
<ExploreBodySearch>
<Layout.Main width="full">
<LogsFilterSection>
<StyledPageFilterBar condensed>
<ProjectPageFilter />
<EnvironmentPageFilter />
<DatePageFilter
{...datePageFilterProps}
searchPlaceholder={t('Custom range: 2h, 4d, 3w')}
/>
</StyledPageFilterBar>
<LogsSearchBar
tracesItemSearchQueryBuilderProps={tracesItemSearchQueryBuilderProps}
/>
{saveAsItems.length > 0 && (
<DropdownMenu
items={saveAsItems}
trigger={triggerProps => (
<Button
{...triggerProps}
priority="default"
aria-label={t('Save as')}
onClick={e => {
e.stopPropagation();
e.preventDefault();

triggerProps.onClick?.(e);
}}
>
{t('Save as')}
</Button>
)}
/>
)}
</LogsFilterSection>
<ExploreSchemaHintsSection>
<SchemaHintsList
supportedAggregates={supportedAggregates}
booleanTags={booleanAttributes}
numberTags={numberAttributes}
stringTags={stringAttributes}
isLoading={
numberAttributesLoading ||
stringAttributesLoading ||
booleanAttributesLoading
}
exploreQuery={logsSearch.formatString()}
source={SchemaHintsSources.LOGS}
searchBarWidthOffset={columnEditorButtonRef.current?.clientWidth}
/>
</ExploreSchemaHintsSection>
</Layout.Main>
</ExploreBodySearch>

<Fragment>
<LogsSearchSection
datePageFilterProps={datePageFilterProps}
searchBarWidthOffset={columnEditorButtonRef.current?.clientWidth}
/>
<ExploreBodyContent>
<ExploreControlSection expanded={sidebarOpen}>
{sidebarOpen ? <LogsToolbar /> : null}
Expand Down Expand Up @@ -482,6 +524,6 @@ export function LogsTabContent({datePageFilterProps}: LogsTabProps) {
</LogsItemContainer>
</ExploreContentSection>
</ExploreBodyContent>
</SearchQueryBuilderProvider>
</Fragment>
);
}
50 changes: 33 additions & 17 deletions static/app/views/explore/logs/useLogsSearchQueryBuilderProps.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useCallback} from 'react';
import {useCallback, useMemo} from 'react';

import {useCaseInsensitivity} from 'sentry/components/searchQueryBuilder/hooks';
import type {TagCollection} from 'sentry/types/group';
Expand Down Expand Up @@ -69,22 +69,38 @@ export function useLogsSearchQueryBuilderProps({
]
);

const tracesItemSearchQueryBuilderProps: TraceItemSearchQueryBuilderProps = {
initialQuery: logsSearch.formatString(),
searchSource: 'ourlogs',
onSearch,
booleanAttributes,
numberAttributes,
stringAttributes,
itemType: TraceItemDataset.LOGS as TraceItemDataset.LOGS,
booleanSecondaryAliases,
numberSecondaryAliases,
stringSecondaryAliases,
caseInsensitive,
onCaseInsensitiveClick: setCaseInsensitive,
replaceRawSearchKeys: hasRawSearchReplacement ? ['message'] : undefined,
matchKeySuggestions: [{key: 'trace', valuePattern: /^[0-9a-fA-F]{32}$/}],
};
const initialQuery = logsSearch.formatString();
const tracesItemSearchQueryBuilderProps = useMemo<TraceItemSearchQueryBuilderProps>(
() => ({
initialQuery,
searchSource: 'ourlogs',
onSearch,
booleanAttributes,
numberAttributes,
stringAttributes,
itemType: TraceItemDataset.LOGS as TraceItemDataset.LOGS,
booleanSecondaryAliases,
numberSecondaryAliases,
stringSecondaryAliases,
caseInsensitive,
onCaseInsensitiveClick: setCaseInsensitive,
replaceRawSearchKeys: hasRawSearchReplacement ? ['message'] : undefined,
matchKeySuggestions: [{key: 'trace', valuePattern: /^[0-9a-fA-F]{32}$/}],
}),
[
booleanAttributes,
booleanSecondaryAliases,
caseInsensitive,
hasRawSearchReplacement,
initialQuery,
numberAttributes,
numberSecondaryAliases,
onSearch,
setCaseInsensitive,
stringAttributes,
stringSecondaryAliases,
]
);

const searchQueryBuilderProviderProps = useTraceItemSearchQueryBuilderProps(
tracesItemSearchQueryBuilderProps
Expand Down
Loading