Logs: more analytics (#115330)

* Logs: more analytics

* LogLineDetails: collect fields data

* analytics: report length count

* Prettier

* LogLineDetailsHeader: track details mode toggle
This commit is contained in:
Matias Chomicki
2025-12-17 10:25:28 +01:00
committed by GitHub
parent fc4c699d85
commit 1b9e0fae8d
5 changed files with 81 additions and 5 deletions
@@ -9,7 +9,7 @@ import { LogListModel, NEWLINES_REGEX } from '../panel/processing';
export const OTEL_PROBE_FIELD = 'severity_number';
const OTEL_LANGUAGE_UNKNOWN = 'unknown';
function identifyOTelLanguages(logs: LogListModel[] | LogRowModel[]): string[] {
export function identifyOTelLanguages(logs: LogListModel[] | LogRowModel[]): string[] {
const languagesSet = new Set<string>();
logs.forEach((log) => {
const lang = identifyOTelLanguage(log);
@@ -1,6 +1,6 @@
import { css } from '@emotion/css';
import { camelCase, groupBy } from 'lodash';
import { memo, startTransition, useCallback, useMemo, useRef, useState } from 'react';
import { memo, startTransition, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { DataFrameType, GrafanaTheme2, store, TimeRange } from '@grafana/data';
import { t, Trans } from '@grafana/i18n';
@@ -18,6 +18,7 @@ import { LogLineDetailsLinks } from './LogLineDetailsLinks';
import { LogLineDetailsLog } from './LogLineDetailsLog';
import { LogLineDetailsTrace } from './LogLineDetailsTrace';
import { useLogListContext } from './LogListContext';
import { reportInteractionOnce } from './analytics';
import { getTempoTraceFromLinks } from './links';
import { LogListModel } from './processing';
@@ -124,6 +125,21 @@ export const LogLineDetailsComponent = memo(
[fieldsWithLinks.links, fieldsWithLinks.linksFromVariableMap]
);
useEffect(() => {
if (noInteractions) {
return;
}
reportInteractionOnce('logs_log_line_details_fields_displayed', {
links: allLinks.length,
trace: trace !== undefined,
fields: fieldsWithoutLinks.length,
labels: labelsWithLinks.length,
labelGroups: labelGroups.join(', '),
});
// Once
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
<LogLineDetailsHeader focusLogLine={focusLogLine} log={log} search={search} onSearch={handleSearch} />
@@ -102,7 +102,11 @@ export const LogLineDetailsHeader = ({ focusLogLine, log, search, onSearch }: Pr
}
setDetailsMode(newMode);
}, [detailsMode, logOptionsStorageKey, setDetailsMode]);
reportInteractionWrapper('logs_log_line_details_header_toggle_details_mode', {
newMode,
});
}, [detailsMode, logOptionsStorageKey, reportInteractionWrapper, setDetailsMode]);
const toggleLogLine = useCallback(() => {
if (logLineDisplayed) {
@@ -34,7 +34,7 @@ import { getDefaultDetailsMode, getDetailsWidth } from './LogDetailsContext';
import { LogLineTimestampResolution } from './LogLine';
import { GetRowContextQueryFn, LogLineMenuCustomItem } from './LogLineMenu';
import { LogListOptions, LogListFontSize } from './LogList';
import { reportInteractionOnce } from './analytics';
import { collectInsights } from './analytics';
import { LogListModel } from './processing';
export interface LogListContextData extends Omit<Props, 'containerElement' | 'logs' | 'logsMeta' | 'showControls'> {
@@ -241,7 +241,7 @@ export const LogListContextProvider = ({
if (noInteractions) {
return;
}
reportInteractionOnce(`logs_log_list_${app}_logs_displayed`, {
collectInsights(logs, app, {
dedupStrategy,
fontSize,
forceEscape: logListState.forceEscape,
@@ -1,5 +1,8 @@
import { CoreApp, LogRowModel } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
import { identifyOTelLanguages } from '../otel/formats';
export const reportInteractionOnce = (interactionName: string, properties?: Record<string, unknown>) => {
const key = `logs.interactions.${interactionName}`;
if (sessionStorage.getItem(key)) {
@@ -8,3 +11,56 @@ export const reportInteractionOnce = (interactionName: string, properties?: Reco
sessionStorage.setItem(key, '1');
reportInteraction(interactionName, properties);
};
export function collectInsights(logs: LogRowModel[], app: CoreApp, properties?: Record<string, unknown>) {
if (!logs.length) {
return;
}
const { longest, shortest, average, median } = getLogsStats(logs);
reportInteractionOnce(`logs_log_list_${app}_logs_displayed`, {
...properties,
otelLanguage: identifyOTelLanguages(logs).join(', '),
ansi: logs.some((logs) => logs.hasAnsi),
unescaped: logs.some((logs) => logs.hasUnescapedContent),
dsType: logs[0]?.datasourceType ?? '',
count: logs.length,
longestLog: longest,
shortestLog: shortest,
averageLog: average,
medianLog: median,
});
}
function getLogsStats(logs: LogRowModel[]) {
let longest = 0,
shortest = logs[0].raw.length,
median = 0;
const lengths: number[] = [];
let sum = 0;
for (let i = 0; i < logs.length; i++) {
let length = logs[i].raw.length;
if (length > longest) {
longest = length;
} else if (length < shortest) {
shortest = length;
}
sum += length;
lengths.push(length);
}
lengths.sort((a, b) => a - b);
const mid = Math.floor(lengths.length / 2);
if (lengths.length % 2 === 0) {
median = (lengths[mid - 1] + lengths[mid]) / 2;
} else {
median = lengths[mid];
}
return { longest, shortest, average: Math.round(sum / logs.length), median };
}