Skip to content

Commit

Permalink
feat(ourlogs): Implement the initial version of the logs tab (#83962)
Browse files Browse the repository at this point in the history
Screenshot, data pulled end-to-end from clickhouse. Search also works.

<img width="1035" alt="Screenshot 2025-01-24 at 3 42 04 PM"
src="https://github.com/user-attachments/assets/e17cd06a-d24e-4dac-b48e-4b1aabec1f64"
/>
  • Loading branch information
colin-sentry authored Jan 24, 2025
1 parent 53c19e5 commit fd52e4c
Show file tree
Hide file tree
Showing 11 changed files with 218 additions and 19 deletions.
5 changes: 5 additions & 0 deletions static/app/components/nav/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ export function createNavConfig({organization}: {organization: Organization}): N
to: `/${prefix}/traces/`,
feature: {features: 'performance-trace-explorer'},
},
{
label: t('Logs'),
to: `/${prefix}/explore/logs/`,
feature: {features: 'ourlogs-enabled'},
},
{
label: t('Metrics'),
to: `/${prefix}/metrics/`,
Expand Down
4 changes: 3 additions & 1 deletion static/app/components/nav/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const ALL_AVAILABLE_FEATURES = [
'custom-metrics',
'user-feedback-ui',
'session-replay-ui',
'ourlogs-enabled',
'performance-view',
'performance-trace-explorer',
'starfish-mobile-ui-module',
Expand Down Expand Up @@ -159,9 +160,10 @@ describe('Nav', function () {
renderNav();
const container = screen.getByRole('navigation', {name: 'Secondary Navigation'});
const links = within(container).getAllByRole('link');
expect(links).toHaveLength(7);
expect(links).toHaveLength(8);
[
'Traces',
'Logs',
'Metrics',
'Profiles',
'Replays',
Expand Down
13 changes: 13 additions & 0 deletions static/app/components/sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,18 @@ function Sidebar() {
</Feature>
);

const logs = hasOrganization && (
<Feature features="ourlogs-enabled">
<SidebarItem
{...sidebarItemProps}
label={<GuideAnchor target="logs">{t('Logs')}</GuideAnchor>}
to={`/organizations/${organization?.slug}/explore/logs/`}
id="ourlogs"
icon={<SubitemDot collapsed />}
/>
</Feature>
);

const performance = hasOrganization && (
<Feature
hookName="feature-disabled:performance-sidebar-item"
Expand Down Expand Up @@ -497,6 +509,7 @@ function Sidebar() {
exact={!shouldAccordionFloat}
>
{traces}
{logs}
{metrics}
{profiling}
{replays}
Expand Down
9 changes: 9 additions & 0 deletions static/app/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1900,6 +1900,14 @@ function buildRoutes() {
</Route>
);

const exploreRoutes = (
<Route
path="/explore/logs"
component={make(() => import('sentry/views/explore/logs'))}
withOrgPath
/>
);

const userFeedbackRoutes = (
<Route
path="/user-feedback/"
Expand Down Expand Up @@ -2323,6 +2331,7 @@ function buildRoutes() {
{performanceRoutes}
{domainViewRoutes}
{tracesRoutes}
{exploreRoutes}
{llmMonitoringRedirects}
{profilingRoutes}
{metricsRoutes}
Expand Down
1 change: 1 addition & 0 deletions static/app/utils/discover/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export enum DiscoverDatasets {
METRICS = 'metrics',
METRICS_ENHANCED = 'metricsEnhanced',
ISSUE_PLATFORM = 'issuePlatform',
OURLOGS = 'ourlogs',
SPANS_EAP = 'spans',
SPANS_EAP_RPC = 'spansRpc',
SPANS_INDEXED = 'spansIndexed',
Expand Down
20 changes: 5 additions & 15 deletions static/app/views/explore/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,10 @@ import PageFiltersContainer from 'sentry/components/organizations/pageFilters/co
import {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionTooltip';
import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
import {t} from 'sentry/locale';
import {decodeScalar} from 'sentry/utils/queryString';
import {useLocation} from 'sentry/utils/useLocation';
import {useNavigate} from 'sentry/utils/useNavigate';
import useOrganization from 'sentry/utils/useOrganization';
import {LogsTabContent} from 'sentry/views/explore/logs/logsTab';
import {SpansTabContent} from 'sentry/views/explore/spans/spansTab';
import TraceExplorerTabs from 'sentry/views/explore/tabBar';
import {limitMaxPickableDays} from 'sentry/views/explore/utils';

export function ExploreContent() {
Expand All @@ -36,8 +33,6 @@ export function ExploreContent() {
},
});
}, [location, navigate]);
const ourlogsEnabled = organization.features.includes('ourlogs-enabled');
const selectedTab = decodeScalar(location.query.exploreTab, 'spans');

return (
<SentryDocumentTitle title={t('Traces')} orgSlug={organization?.slug}>
Expand Down Expand Up @@ -72,17 +67,12 @@ export function ExploreContent() {
<FeedbackWidgetButton />
</ButtonBar>
</Layout.HeaderActions>
{ourlogsEnabled ? <TraceExplorerTabs selected={selectedTab} /> : null}
</Layout.Header>
{ourlogsEnabled && selectedTab === 'logs' ? (
<LogsTabContent />
) : (
<SpansTabContent
defaultPeriod={defaultPeriod}
maxPickableDays={maxPickableDays}
relativeOptions={relativeOptions}
/>
)}
<SpansTabContent
defaultPeriod={defaultPeriod}
maxPickableDays={maxPickableDays}
relativeOptions={relativeOptions}
/>
</Layout.Page>
</PageFiltersContainer>
</SentryDocumentTitle>
Expand Down
37 changes: 37 additions & 0 deletions static/app/views/explore/logs/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import FeatureBadge from 'sentry/components/badge/featureBadge';
import * as Layout from 'sentry/components/layouts/thirds';
import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container';
import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
import {t} from 'sentry/locale';
import useOrganization from 'sentry/utils/useOrganization';
import {LogsTabContent} from 'sentry/views/explore/logs/logsTab';
import {limitMaxPickableDays} from 'sentry/views/explore/utils';

export default function LogsPage() {
const organization = useOrganization();
const {defaultPeriod, maxPickableDays, relativeOptions} = limitMaxPickableDays(
organization!
);

return (
<SentryDocumentTitle title={t('Logs')} orgSlug={organization?.slug}>
<PageFiltersContainer maxPickableDays={maxPickableDays}>
<Layout.Page>
<Layout.Header>
<Layout.HeaderContent>
<Layout.Title>
{t('Logs')}
<FeatureBadge type="experimental" />
</Layout.Title>
</Layout.HeaderContent>
</Layout.Header>
<LogsTabContent
defaultPeriod={defaultPeriod}
maxPickableDays={maxPickableDays}
relativeOptions={relativeOptions}
/>
</Layout.Page>
</PageFiltersContainer>
</SentryDocumentTitle>
);
}
70 changes: 67 additions & 3 deletions static/app/views/explore/logs/logsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,68 @@
export function LogsTabContent() {
// TODO implement
return <div>logs!</div>;
import {useState} from 'react';
import styled from '@emotion/styled';

import * as Layout from 'sentry/components/layouts/thirds';
import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
import {SearchQueryBuilder} from 'sentry/components/searchQueryBuilder';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
import {LogsTable} from 'sentry/views/explore/logs/logsTable';
import type {DefaultPeriod, MaxPickableDays} from 'sentry/views/explore/utils';

export type LogsTabProps = {
defaultPeriod: DefaultPeriod;
maxPickableDays: MaxPickableDays;
relativeOptions: Record<string, React.ReactNode>;
};

export function LogsTabContent({
defaultPeriod,
maxPickableDays,
relativeOptions,
}: LogsTabProps) {
const [search, setSearch] = useState(new MutableSearch(''));
return (
<Layout.Body>
<Layout.Main fullWidth>
<FilterBarContainer>
<PageFilterBar condensed>
<ProjectPageFilter />
<EnvironmentPageFilter />
<DatePageFilter
defaultPeriod={defaultPeriod}
maxPickableDays={maxPickableDays}
relativeOptions={({arbitraryOptions}) => ({
...arbitraryOptions,
...relativeOptions,
})}
/>
</PageFilterBar>
<SearchQueryBuilder
placeholder={t('Search for logs')}
filterKeys={{}}
getTagValues={() => new Promise<string[]>(() => [])}
initialQuery=""
searchSource="ourlogs"
onSearch={query => setSearch(new MutableSearch(query))}
/>
</FilterBarContainer>
</Layout.Main>
<LogsTableContainer fullWidth>
<LogsTable search={search} />
</LogsTableContainer>
</Layout.Body>
);
}

const FilterBarContainer = styled('div')`
display: flex;
gap: ${space(2)};
`;

const LogsTableContainer = styled(Layout.Main)`
margin-top: ${space(2)};
`;
58 changes: 58 additions & 0 deletions static/app/views/explore/logs/logsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import GridEditable from 'sentry/components/gridEditable';
import TimeSince from 'sentry/components/timeSince';
import type {MutableSearch} from 'sentry/utils/tokenizeSearch';
import {useOurlogs} from 'sentry/views/insights/common/queries/useDiscover';
import type {OurlogsFields} from 'sentry/views/insights/types';

export type LogsTableProps = {
search: MutableSearch;
};

export function LogsTable(props: LogsTableProps) {
const {data, error, isPending} = useOurlogs(
{
limit: 100,
sorts: [],
fields: ['sentry.severity_text', 'sentry.body', 'sentry.timestamp'],
search: props.search,
},
'api.logs-tab.view'
);
return (
<GridEditable<OurlogsFields, keyof OurlogsFields>
isLoading={isPending}
columnOrder={[
{key: 'sentry.severity_text', name: 'Severity', width: 60},
{key: 'sentry.body', name: 'Body', width: 500},
{key: 'sentry.timestamp', name: 'Timestamp', width: 90},
]}
columnSortBy={[]}
data={data}
error={error}
grid={{
renderHeadCell: col => {
return <div>{col.name}</div>;
},
renderBodyCell: (col, dataRow) => {
if (col.key === 'sentry.timestamp') {
return timestampRenderer(dataRow['sentry.timestamp']);
}
if (col.key === 'sentry.severity_text') {
return severityTextRenderer(dataRow['sentry.severity_text']);
}
return <div>{dataRow[col.key]}</div>;
},
}}
/>
);
}

function severityTextRenderer(text: string) {
return <div>{text.toUpperCase().slice(0, 4)}</div>;
}

function timestampRenderer(timestamp: string) {
return (
<TimeSince unitStyle="extraShort" date={new Date(timestamp)} tooltipShowSeconds />
);
}
14 changes: 14 additions & 0 deletions static/app/views/insights/common/queries/useDiscover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
SpanMetricsProperty,
SpanMetricsResponse,
} from 'sentry/views/insights/types';
import type {OurlogsFields} from 'sentry/views/insights/types';

interface UseMetricsOptions<Fields> {
cursor?: string;
Expand All @@ -38,6 +39,19 @@ export const useSpansIndexed = <Fields extends SpanIndexedProperty[]>(
);
};

export const useOurlogs = <Fields extends Array<keyof OurlogsFields>>(
options: UseMetricsOptions<Fields> = {},
referrer: string
) => {
const {data, ...rest} = useDiscover<Fields, OurlogsFields>(
options,
DiscoverDatasets.OURLOGS,
referrer
);
const castData = data as OurlogsFields[];
return {...rest, data: castData};
};

export const useEAPSpans = <Fields extends EAPSpanProperty[]>(
options: UseMetricsOptions<Fields> = {},
referrer: string
Expand Down
6 changes: 6 additions & 0 deletions static/app/views/insights/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -415,3 +415,9 @@ export const subregionCodeToName = {
};

export type SubregionCode = keyof typeof subregionCodeToName;

export type OurlogsFields = {
'sentry.body': string;
'sentry.severity_text': string;
'sentry.timestamp': string;
};

0 comments on commit fd52e4c

Please sign in to comment.