Observability · 2024-10-28 · 3 min read
Dynatrace Custom Apps: Correlating Logs Across Distributed Layers
Dynatrace is great for creating simple and complex dashboards and notebooks. While the use-case below can be solved with a standard dashboard, it serves as a clean, practical blueprint for building custom apps in Dynatrace AppEngine.
Our Use-Case
Our setup consists of two decoupled systems—a booking system and a processing layer:
- The booking system takes orders, saves them to a database, and logs them with a
trx_id(transaction ID) and a newly createdsession_id. - The processing layer picks up the order and processes it via several transformations. However, subsequent log entries only carry the
session_id, not the originaltrx_id.
We want to give engineers and support teams a simple form where they can enter a trx_id and automatically retrieve all log entries across both systems.
Querying with DQL
First, we match on the trx_id to extract the session_id, and then run a DQL query to stitch the full lifecycle together:
fetch logs
| parse content, """DATA 'trx_id:'LD:trx_id', session_id:'LD:session_id"""
| parse content, """DATA 'session_id:'LD:session_idx','"""
| filter isNotNull(trx_id) or isNotNull(session_idx)
| filter matchesValue(session_id, "465654-65454-5458") or matchesValue(session_idx, "465654-65454-5458")
| sort timestamp
| fieldsAdd session_id = if(isNull(session_id), session_idx, else: session_id)
| fields trx_id, session_id, content
Building the Dynatrace React App
Using @dynatrace/strato-components and @dynatrace-sdk/react-hooks, we can chain the two DQL queries cleanly inside a React component:
import React, { useState } from 'react';
import { Flex } from '@dynatrace/strato-components/layouts';
import { TitleBar } from '@dynatrace/strato-components-preview/layouts';
import { DataTable, TableColumn } from '@dynatrace/strato-components-preview/tables';
import { ProgressCircle } from '@dynatrace/strato-components/content';
import { useDqlQuery } from '@dynatrace-sdk/react-hooks';
import { FormField, Label, TextInput } from '@dynatrace/strato-components-preview/forms';
import { Button } from '@dynatrace/strato-components/buttons';
import { logTrxQuery, logSessionQuery } from '../queries';
function ResultsTable({ trxid }: { trxid: string }) {
const columns: TableColumn[] = [
{ header: 'Transaction ID', accessor: 'trx_id', autoWidth: true },
{ header: 'Session ID', accessor: 'session_id', autoWidth: true },
{ header: 'Content', accessor: 'content', autoWidth: true },
];
const result = useDqlQuery({ body: { query: logTrxQuery(trxid) } });
let session_id = "";
if (!result.isLoading) {
result?.data?.records.forEach((value) => {
session_id = value?.session_id?.toString() ?? "";
});
}
const resultSess = useDqlQuery({ body: { query: logSessionQuery(session_id) } });
if (resultSess.isLoading) return <ProgressCircle />;
return (
<DataTable data={resultSess?.data?.records || []} columns={columns}>
<DataTable.Pagination />
</DataTable>
);
}
export const LogMesh = () => {
const [trxid, setTrxid] = useState("");
const [sent, setSent] = useState(false);
return (
<Flex width="100%" flexDirection="column" justifyContent="center" gap={16}>
<TitleBar>
<TitleBar.Title>Log Meshing</TitleBar.Title>
</TitleBar>
<FormField>
<Label>Enter Transaction ID</Label>
<TextInput
type="text"
id="trxid"
name="trxid"
value={trxid}
onChange={(val) => { setTrxid(val); setSent(false); }}
/>
<Button onClick={() => setSent(true)} variant="emphasized">Search</Button>
{sent && <ResultsTable trxid={trxid} />}
</FormField>
</Flex>
);
};
If you'd like the full repository or need help building custom Dynatrace extensions and apps, get in touch.
Enjoyed this technical breakdown?
Subscribe for occasional engineering field notes on load testing, synthetic data, and observability—or talk to us about your system.