Logs.tsx 8.9 KB

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