Logs.tsx 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. import _ from 'lodash';
  2. import React, { PureComponent } from 'react';
  3. import { rangeUtil } from '@grafana/data';
  4. import { Switch } from '@grafana/ui';
  5. import {
  6. RawTimeRange,
  7. LogLevel,
  8. TimeZone,
  9. AbsoluteTimeRange,
  10. LogsMetaKind,
  11. LogsModel,
  12. LogsDedupStrategy,
  13. LogRowModel,
  14. } from '@grafana/data';
  15. import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup';
  16. import { LogLabels } from './LogLabels';
  17. import { LogRow } from './LogRow';
  18. import { LogsDedupDescription } from 'app/core/logs_model';
  19. import ExploreGraphPanel from './ExploreGraphPanel';
  20. import { ExploreId } from 'app/types';
  21. const PREVIEW_LIMIT = 100;
  22. function renderMetaItem(value: any, kind: LogsMetaKind) {
  23. if (kind === LogsMetaKind.LabelsMap) {
  24. return (
  25. <span className="logs-meta-item__labels">
  26. <LogLabels labels={value} plain />
  27. </span>
  28. );
  29. }
  30. return value;
  31. }
  32. interface Props {
  33. data?: LogsModel;
  34. dedupedData?: LogsModel;
  35. width: number;
  36. exploreId: ExploreId;
  37. highlighterExpressions: string[];
  38. loading: boolean;
  39. absoluteRange: AbsoluteTimeRange;
  40. timeZone: TimeZone;
  41. scanning?: boolean;
  42. scanRange?: RawTimeRange;
  43. dedupStrategy: LogsDedupStrategy;
  44. hiddenLogLevels: Set<LogLevel>;
  45. onChangeTime?: (range: AbsoluteTimeRange) => void;
  46. onClickLabel?: (label: string, value: string) => void;
  47. onStartScanning?: () => void;
  48. onStopScanning?: () => void;
  49. onDedupStrategyChange: (dedupStrategy: LogsDedupStrategy) => void;
  50. onToggleLogLevel: (hiddenLogLevels: LogLevel[]) => void;
  51. getRowContext?: (row: LogRowModel, options?: any) => Promise<any>;
  52. }
  53. interface State {
  54. deferLogs: boolean;
  55. renderAll: boolean;
  56. showLabels: boolean;
  57. showTime: boolean;
  58. }
  59. export default class Logs extends PureComponent<Props, State> {
  60. deferLogsTimer: NodeJS.Timer;
  61. renderAllTimer: NodeJS.Timer;
  62. state = {
  63. deferLogs: true,
  64. renderAll: false,
  65. showLabels: false,
  66. showTime: true,
  67. };
  68. componentDidMount() {
  69. // Staged rendering
  70. if (this.state.deferLogs) {
  71. const { data } = this.props;
  72. const rowCount = data && data.rows ? data.rows.length : 0;
  73. // Render all right away if not too far over the limit
  74. const renderAll = rowCount <= PREVIEW_LIMIT * 2;
  75. this.deferLogsTimer = setTimeout(() => this.setState({ deferLogs: false, renderAll }), rowCount);
  76. }
  77. }
  78. componentDidUpdate(prevProps: Props, prevState: State) {
  79. // Staged rendering
  80. if (prevState.deferLogs && !this.state.deferLogs && !this.state.renderAll) {
  81. this.renderAllTimer = setTimeout(() => this.setState({ renderAll: true }), 2000);
  82. }
  83. }
  84. componentWillUnmount() {
  85. clearTimeout(this.deferLogsTimer);
  86. clearTimeout(this.renderAllTimer);
  87. }
  88. onChangeDedup = (dedup: LogsDedupStrategy) => {
  89. const { onDedupStrategyChange } = this.props;
  90. if (this.props.dedupStrategy === dedup) {
  91. return onDedupStrategyChange(LogsDedupStrategy.none);
  92. }
  93. return onDedupStrategyChange(dedup);
  94. };
  95. onChangeLabels = (event: React.SyntheticEvent) => {
  96. const target = event.target as HTMLInputElement;
  97. this.setState({
  98. showLabels: target.checked,
  99. });
  100. };
  101. onChangeTime = (event: React.SyntheticEvent) => {
  102. const target = event.target as HTMLInputElement;
  103. this.setState({
  104. showTime: target.checked,
  105. });
  106. };
  107. onToggleLogLevel = (hiddenRawLevels: string[]) => {
  108. const hiddenLogLevels: LogLevel[] = hiddenRawLevels.map((level: LogLevel) => LogLevel[level]);
  109. this.props.onToggleLogLevel(hiddenLogLevels);
  110. };
  111. onClickScan = (event: React.SyntheticEvent) => {
  112. event.preventDefault();
  113. this.props.onStartScanning();
  114. };
  115. onClickStopScan = (event: React.SyntheticEvent) => {
  116. event.preventDefault();
  117. this.props.onStopScanning();
  118. };
  119. render() {
  120. const {
  121. data,
  122. exploreId,
  123. highlighterExpressions,
  124. loading = false,
  125. onClickLabel,
  126. timeZone,
  127. scanning,
  128. scanRange,
  129. width,
  130. dedupedData,
  131. } = this.props;
  132. if (!data) {
  133. return null;
  134. }
  135. const { deferLogs, renderAll, showLabels, showTime } = this.state;
  136. const { dedupStrategy } = this.props;
  137. const hasData = data && data.rows && data.rows.length > 0;
  138. const hasLabel = hasData && dedupedData.hasUniqueLabels;
  139. const dedupCount = dedupedData.rows.reduce((sum, row) => sum + row.duplicates, 0);
  140. const showDuplicates = dedupStrategy !== LogsDedupStrategy.none && dedupCount > 0;
  141. const meta = data.meta ? [...data.meta] : [];
  142. if (dedupStrategy !== LogsDedupStrategy.none) {
  143. meta.push({
  144. label: 'Dedup count',
  145. value: dedupCount,
  146. kind: LogsMetaKind.Number,
  147. });
  148. }
  149. // Staged rendering
  150. const processedRows = dedupedData.rows;
  151. const firstRows = processedRows.slice(0, PREVIEW_LIMIT);
  152. const lastRows = processedRows.slice(PREVIEW_LIMIT);
  153. const scanText = scanRange ? `Scanning ${rangeUtil.describeTimeRange(scanRange)}` : 'Scanning...';
  154. // React profiler becomes unusable if we pass all rows to all rows and their labels, using getter instead
  155. const getRows = () => processedRows;
  156. return (
  157. <div className="logs-panel">
  158. <div className="logs-panel-graph">
  159. <ExploreGraphPanel
  160. exploreId={exploreId}
  161. series={data.series}
  162. width={width}
  163. onHiddenSeriesChanged={this.onToggleLogLevel}
  164. />
  165. </div>
  166. <div className="logs-panel-options">
  167. <div className="logs-panel-controls">
  168. <Switch label="Time" checked={showTime} onChange={this.onChangeTime} transparent />
  169. <Switch label="Labels" checked={showLabels} onChange={this.onChangeLabels} transparent />
  170. <ToggleButtonGroup label="Dedup" transparent={true}>
  171. {Object.keys(LogsDedupStrategy).map((dedupType: string, i) => (
  172. <ToggleButton
  173. key={i}
  174. value={dedupType}
  175. onChange={this.onChangeDedup}
  176. selected={dedupStrategy === dedupType}
  177. // @ts-ignore
  178. tooltip={LogsDedupDescription[dedupType]}
  179. >
  180. {dedupType}
  181. </ToggleButton>
  182. ))}
  183. </ToggleButtonGroup>
  184. </div>
  185. </div>
  186. {hasData && meta && (
  187. <div className="logs-panel-meta">
  188. {meta.map(item => (
  189. <div className="logs-panel-meta__item" key={item.label}>
  190. <span className="logs-panel-meta__label">{item.label}:</span>
  191. <span className="logs-panel-meta__value">{renderMetaItem(item.value, item.kind)}</span>
  192. </div>
  193. ))}
  194. </div>
  195. )}
  196. <div className="logs-rows">
  197. {hasData &&
  198. !deferLogs && // Only inject highlighterExpression in the first set for performance reasons
  199. firstRows.map((row, index) => (
  200. <LogRow
  201. key={index}
  202. getRows={getRows}
  203. getRowContext={this.props.getRowContext}
  204. highlighterExpressions={highlighterExpressions}
  205. row={row}
  206. showDuplicates={showDuplicates}
  207. showLabels={showLabels && hasLabel}
  208. showTime={showTime}
  209. timeZone={timeZone}
  210. onClickLabel={onClickLabel}
  211. />
  212. ))}
  213. {hasData &&
  214. !deferLogs &&
  215. renderAll &&
  216. lastRows.map((row, index) => (
  217. <LogRow
  218. key={PREVIEW_LIMIT + index}
  219. getRows={getRows}
  220. getRowContext={this.props.getRowContext}
  221. row={row}
  222. showDuplicates={showDuplicates}
  223. showLabels={showLabels && hasLabel}
  224. showTime={showTime}
  225. timeZone={timeZone}
  226. onClickLabel={onClickLabel}
  227. />
  228. ))}
  229. {hasData && deferLogs && <span>Rendering {dedupedData.rows.length} rows...</span>}
  230. </div>
  231. {!loading && !hasData && !scanning && (
  232. <div className="logs-panel-nodata">
  233. No logs found.
  234. <a className="link" onClick={this.onClickScan}>
  235. Scan for older logs
  236. </a>
  237. </div>
  238. )}
  239. {scanning && (
  240. <div className="logs-panel-nodata">
  241. <span>{scanText}</span>
  242. <a className="link" onClick={this.onClickStopScan}>
  243. Stop scan
  244. </a>
  245. </div>
  246. )}
  247. </div>
  248. );
  249. }
  250. }