Logs.tsx 9.4 KB

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