diff --git a/public/app/features/logs/components/otel/formats.ts b/public/app/features/logs/components/otel/formats.ts index c767a69a89e..23e6ec3559f 100644 --- a/public/app/features/logs/components/otel/formats.ts +++ b/public/app/features/logs/components/otel/formats.ts @@ -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(); logs.forEach((log) => { const lang = identifyOTelLanguage(log); diff --git a/public/app/features/logs/components/panel/LogLineDetailsComponent.tsx b/public/app/features/logs/components/panel/LogLineDetailsComponent.tsx index 36bdbf2bdde..0afd2e21369 100644 --- a/public/app/features/logs/components/panel/LogLineDetailsComponent.tsx +++ b/public/app/features/logs/components/panel/LogLineDetailsComponent.tsx @@ -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 ( <> diff --git a/public/app/features/logs/components/panel/LogLineDetailsHeader.tsx b/public/app/features/logs/components/panel/LogLineDetailsHeader.tsx index 34dd3b3b151..6968c4c138c 100644 --- a/public/app/features/logs/components/panel/LogLineDetailsHeader.tsx +++ b/public/app/features/logs/components/panel/LogLineDetailsHeader.tsx @@ -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) { diff --git a/public/app/features/logs/components/panel/LogListContext.tsx b/public/app/features/logs/components/panel/LogListContext.tsx index 139f40ea98e..e7108af0881 100644 --- a/public/app/features/logs/components/panel/LogListContext.tsx +++ b/public/app/features/logs/components/panel/LogListContext.tsx @@ -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 { @@ -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, diff --git a/public/app/features/logs/components/panel/analytics.ts b/public/app/features/logs/components/panel/analytics.ts index 39ec415b939..54d819debb3 100644 --- a/public/app/features/logs/components/panel/analytics.ts +++ b/public/app/features/logs/components/panel/analytics.ts @@ -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) => { 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) { + 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 }; +}