Logs.tsx 6.3 KB

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