Logs.tsx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. import React, { Fragment, PureComponent } from 'react';
  2. import Highlighter from 'react-highlight-words';
  3. import * as rangeUtil from 'app/core/utils/rangeutil';
  4. import { RawTimeRange } from 'app/types/series';
  5. import { LogsDedupStrategy, LogsModel, dedupLogRows, filterLogLevels, LogLevel } from 'app/core/logs_model';
  6. import { findHighlightChunksInText } from 'app/core/utils/text';
  7. import { Switch } from 'app/core/components/Switch/Switch';
  8. import Graph from './Graph';
  9. const graphOptions = {
  10. series: {
  11. bars: {
  12. show: true,
  13. lineWidth: 5,
  14. // barWidth: 10,
  15. },
  16. // stack: true,
  17. },
  18. yaxis: {
  19. tickDecimals: 0,
  20. },
  21. };
  22. interface LogsProps {
  23. className?: string;
  24. data: LogsModel;
  25. loading: boolean;
  26. position: string;
  27. range?: RawTimeRange;
  28. scanning?: boolean;
  29. scanRange?: RawTimeRange;
  30. onChangeTime?: (range: RawTimeRange) => void;
  31. onStartScanning?: () => void;
  32. onStopScanning?: () => void;
  33. }
  34. interface LogsState {
  35. dedup: LogsDedupStrategy;
  36. hiddenLogLevels: Set<LogLevel>;
  37. showLabels: boolean;
  38. showLocalTime: boolean;
  39. showUtc: boolean;
  40. }
  41. export default class Logs extends PureComponent<LogsProps, LogsState> {
  42. state = {
  43. dedup: LogsDedupStrategy.none,
  44. hiddenLogLevels: new Set(),
  45. showLabels: true,
  46. showLocalTime: true,
  47. showUtc: false,
  48. };
  49. onChangeDedup = (dedup: LogsDedupStrategy) => {
  50. this.setState(prevState => {
  51. if (prevState.dedup === dedup) {
  52. return { dedup: LogsDedupStrategy.none };
  53. }
  54. return { dedup };
  55. });
  56. };
  57. onChangeLabels = (event: React.SyntheticEvent) => {
  58. const target = event.target as HTMLInputElement;
  59. this.setState({
  60. showLabels: target.checked,
  61. });
  62. };
  63. onChangeLocalTime = (event: React.SyntheticEvent) => {
  64. const target = event.target as HTMLInputElement;
  65. this.setState({
  66. showLocalTime: target.checked,
  67. });
  68. };
  69. onChangeUtc = (event: React.SyntheticEvent) => {
  70. const target = event.target as HTMLInputElement;
  71. this.setState({
  72. showUtc: target.checked,
  73. });
  74. };
  75. onToggleLogLevel = (rawLevel: string, hiddenRawLevels: Set<string>) => {
  76. const hiddenLogLevels: Set<LogLevel> = new Set(Array.from(hiddenRawLevels).map(level => LogLevel[level]));
  77. this.setState({ hiddenLogLevels });
  78. };
  79. onClickScan = (event: React.SyntheticEvent) => {
  80. event.preventDefault();
  81. this.props.onStartScanning();
  82. };
  83. onClickStopScan = (event: React.SyntheticEvent) => {
  84. event.preventDefault();
  85. this.props.onStopScanning();
  86. };
  87. render() {
  88. const { className = '', data, loading = false, position, range, scanning, scanRange } = this.props;
  89. const { dedup, hiddenLogLevels, showLabels, showLocalTime, showUtc } = this.state;
  90. const hasData = data && data.rows && data.rows.length > 0;
  91. const filteredData = filterLogLevels(data, hiddenLogLevels);
  92. const dedupedData = dedupLogRows(filteredData, dedup);
  93. const dedupCount = dedupedData.rows.reduce((sum, row) => sum + row.duplicates, 0);
  94. const meta = [...data.meta];
  95. if (dedup !== LogsDedupStrategy.none) {
  96. meta.push({
  97. label: 'Dedup count',
  98. value: String(dedupCount),
  99. });
  100. }
  101. const cssColumnSizes = ['3px']; // Log-level indicator line
  102. if (showUtc) {
  103. cssColumnSizes.push('minmax(100px, max-content)');
  104. }
  105. if (showLocalTime) {
  106. cssColumnSizes.push('minmax(100px, max-content)');
  107. }
  108. if (showLabels) {
  109. cssColumnSizes.push('minmax(100px, 25%)');
  110. }
  111. cssColumnSizes.push('1fr');
  112. const logEntriesStyle = {
  113. gridTemplateColumns: cssColumnSizes.join(' '),
  114. };
  115. const scanText = scanRange ? `Scanning ${rangeUtil.describeTimeRange(scanRange)}` : 'Scanning...';
  116. return (
  117. <div className={`${className} logs`}>
  118. <div className="logs-graph">
  119. <Graph
  120. data={data.series}
  121. height="100px"
  122. range={range}
  123. id={`explore-logs-graph-${position}`}
  124. onChangeTime={this.props.onChangeTime}
  125. onToggleSeries={this.onToggleLogLevel}
  126. userOptions={graphOptions}
  127. />
  128. </div>
  129. <div className="logs-options">
  130. <div className="logs-controls">
  131. <Switch label="Timestamp" checked={showUtc} onChange={this.onChangeUtc} small />
  132. <Switch label="Local time" checked={showLocalTime} onChange={this.onChangeLocalTime} small />
  133. <Switch label="Labels" checked={showLabels} onChange={this.onChangeLabels} small />
  134. <Switch
  135. label="Dedup: off"
  136. checked={dedup === LogsDedupStrategy.none}
  137. onChange={() => this.onChangeDedup(LogsDedupStrategy.none)}
  138. small
  139. />
  140. <Switch
  141. label="Dedup: exact"
  142. checked={dedup === LogsDedupStrategy.exact}
  143. onChange={() => this.onChangeDedup(LogsDedupStrategy.exact)}
  144. small
  145. />
  146. <Switch
  147. label="Dedup: numbers"
  148. checked={dedup === LogsDedupStrategy.numbers}
  149. onChange={() => this.onChangeDedup(LogsDedupStrategy.numbers)}
  150. small
  151. />
  152. <Switch
  153. label="Dedup: signature"
  154. checked={dedup === LogsDedupStrategy.signature}
  155. onChange={() => this.onChangeDedup(LogsDedupStrategy.signature)}
  156. small
  157. />
  158. {hasData &&
  159. meta && (
  160. <div className="logs-meta">
  161. {meta.map(item => (
  162. <div className="logs-meta-item" key={item.label}>
  163. <span className="logs-meta-item__label">{item.label}:</span>
  164. <span className="logs-meta-item__value">{item.value}</span>
  165. </div>
  166. ))}
  167. </div>
  168. )}
  169. </div>
  170. </div>
  171. <div className="logs-entries" style={logEntriesStyle}>
  172. {hasData &&
  173. dedupedData.rows.map(row => (
  174. <Fragment key={row.key + row.duplicates}>
  175. <div className={row.logLevel ? `logs-row-level logs-row-level-${row.logLevel}` : ''}>
  176. {row.duplicates > 0 && (
  177. <div className="logs-row-level__duplicates" title={`${row.duplicates} duplicates`}>
  178. {Array.apply(null, { length: row.duplicates }).map((bogus, index) => (
  179. <div className="logs-row-level__duplicate" key={`${index}`} />
  180. ))}
  181. </div>
  182. )}
  183. </div>
  184. {showUtc && <div title={`Local: ${row.timeLocal} (${row.timeFromNow})`}>{row.timestamp}</div>}
  185. {showLocalTime && <div title={`${row.timestamp} (${row.timeFromNow})`}>{row.timeLocal}</div>}
  186. {showLabels && (
  187. <div className="max-width" title={row.labels}>
  188. {row.labels}
  189. </div>
  190. )}
  191. <div>
  192. <Highlighter
  193. textToHighlight={row.entry}
  194. searchWords={row.searchWords}
  195. findChunks={findHighlightChunksInText}
  196. highlightClassName="logs-row-match-highlight"
  197. />
  198. </div>
  199. </Fragment>
  200. ))}
  201. </div>
  202. {!loading &&
  203. !hasData &&
  204. !scanning && (
  205. <div className="logs-nodata">
  206. No logs found.
  207. <a className="link" onClick={this.onClickScan}>
  208. Scan for older logs
  209. </a>
  210. </div>
  211. )}
  212. {scanning && (
  213. <div className="logs-nodata">
  214. <span>{scanText}</span>
  215. <a className="link" onClick={this.onClickStopScan}>
  216. Stop scan
  217. </a>
  218. </div>
  219. )}
  220. </div>
  221. );
  222. }
  223. }