Skip to main content

Create logs viewer

In this guide, you'll learn how to create simple visualizations to gather insights from your log data.

App Screenshot App Screenshot

The LogStatusDistribution component

In the LogStatusDistribution component, we'll visualize the log status distribution over time in a bar chart. We'll use DQL to fetch the logs data via the useDqlQuery hook and the TimeseriesChart component for the visualization, with a handy predefined log status color palette.

The DQL query is straightforward. Using the makeTimeseries command, we can convert the raw log records to time series. We need to also convert the DQL result to the appropriate type for the TimeseriesChart using the convertQueryResultToTimeseries function. As we know, DQL doesn't guarantee any specific order in the results, so if we want a consistent and semantically valid visualization, we need to sort the time series data according to the status severity.

src/app/components/LogStatusDistribution.tsx
import React from 'react';
import { useDqlQuery } from '@dynatrace-sdk/react-hooks';
import { Flex } from '@dynatrace/strato-components/layouts';
import { ProgressCircle } from '@dynatrace/strato-components/content';
import { Heading } from '@dynatrace/strato-components/typography';
import {
convertQueryResultToTimeseries,
TimeseriesWithDimensions,
} from '@dynatrace/strato-components-preview/conversion-utilities';
import { TimeseriesChart } from '@dynatrace/strato-components-preview/charts';
import { Surface } from '@dynatrace/strato-components-preview/layouts-core';
import { QueryResult } from '@dynatrace-sdk/client-query';

const LOG_STATUS_QUERY = `fetch logs
| makeTimeseries count(), by:{status}, interval:5m`;

const getSeriesName = (series: TimeseriesWithDimensions) => {
return Array.isArray(series.name) ? series.name[0] : series.name;
};

const buildTimeseries = (result: QueryResult) => {
const timeseries = convertQueryResultToTimeseries(result);
const order = ['ERROR', 'WARN', 'INFO', 'NONE'];
timeseries.sort((a, b) => order.indexOf(getSeriesName(a)) - order.indexOf(getSeriesName(b)));

return timeseries;
};

export const LogStatusDistribution = () => {
const logSeries = useDqlQuery({ body: { query: LOG_STATUS_QUERY } });

return (
<Surface>
{logSeries.isLoading && <ProgressCircle />}
{logSeries.data && (
<Flex flexDirection="column" gap={24}>
<Heading level={3}>Log record distribution</Heading>
<TimeseriesChart colorPalette="log-status" data={buildTimeseries(logSeries.data)} variant="bar" />
</Flex>
)}
</Surface>
);
};
Tip

This operation requires the following scopes:

  • storage:buckets:read
  • storage:logs:read
Read more about scopes in this guide.

The LogRecords component

The LogRecords component consists of a table with the latest logs generated in our environment. Similarly to how we did in the LogStatusDistribution component, we'll use DQL and the useDqlQuery hook, but we'll use the DataTable component for the visualization.

The DQL part is relatively simple. We use the fields command to retrieve only the values that interest us, and then we sort them by timestamp. The DataTable component understands the DQL response format in this case, so no conversion is needed.

The DataTable component is highly customizable. For our use case, we'll configure different colors for the log status using thresholds and color tokens for instant visual feedback, depending on the severity. We'll also add a toolbar with useful actions, the ability to sort the table, and pagination.

src/app/components/LogRecords.tsx
import React from 'react';
import { useDqlQuery } from '@dynatrace-sdk/react-hooks';
import { ProgressCircle } from '@dynatrace/strato-components/content';
import { Heading } from '@dynatrace/strato-components/typography';
import { DataTable, TableColumn } from '@dynatrace/strato-components-preview/tables';
import { Surface } from '@dynatrace/strato-components-preview/layouts-core';
import { Colors } from '@dynatrace/strato-design-tokens';

const LOG_RECORDS_QUERY = `fetch logs
| fields timestamp, status, content
| sort timestamp desc`;

const columns: TableColumn[] = [
{
header: 'Timestamp',
accessor: 'timestamp',
autoWidth: true,
columnType: 'date',
},
{
header: 'Status',
accessor: 'status',
autoWidth: true,
thresholds: [
{
value: 'INFO',
comparator: 'equal-to',
color: Colors.Charts.Logstatus.Info.Default,
},
{
value: 'WARN',
comparator: 'equal-to',
color: Colors.Charts.Logstatus.Warning.Default,
},
{
value: 'ERROR',
comparator: 'equal-to',
color: Colors.Charts.Logstatus.Error.Default,
},
],
},
{
header: 'Content',
accessor: 'content',
},
];

export const LogRecords = () => {
const logRecords = useDqlQuery({ body: { query: LOG_RECORDS_QUERY } });

return (
<Surface>
{logRecords.isLoading && <ProgressCircle />}
{logRecords.data && (
<>
<Heading level={3}>Most recent {logRecords.data.records.length} log records</Heading>
<DataTable
sortable
sortBy={{
id: 'timestamp',
desc: true,
}}
data={logRecords.data.records}
columns={columns}
lineWrap
>
<DataTable.Toolbar>
<DataTable.DownloadData />
<DataTable.LineWrap />
</DataTable.Toolbar>
<DataTable.Pagination defaultPageSize={20} />
</DataTable>
</>
)}
</Surface>
);
};

Putting it together

The last piece of the puzzle is using these components somewhere. We can simply instantiate them in the App component, and we're done.

import React from 'react';
import { Heading } from '@dynatrace/strato-components/typography';
import { Flex } from '@dynatrace/strato-components/layouts';
import { Page } from '@dynatrace/strato-components-preview/layouts';
import { AppHeader, AppName } from '@dynatrace/strato-components-preview/layouts';
import { LogStatusDistribution } from './components/LogStatusDistribution';
import { LogRecords } from './components/LogRecords';

export const App = () => {
return (
<Page>
<Page.Header>
<AppHeader>
<AppName />
</AppHeader>
</Page.Header>
<Page.Main>
<Flex padding={16} flexDirection="column">
<Heading level={2}>Logs Viewer</Heading>
<LogStatusDistribution />
<LogRecords />
</Flex>
</Page.Main>
</Page>
);
};

Summary

Just like that, we've created a basic Logs Viewer app in no time. We hope you found this guide helpful and got the inspiration to build more Dynatrace Apps.

Still have questions?
Find answers in the Dynatrace Community