result_transformer.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import _ from 'lodash';
  2. import moment from 'moment';
  3. import { LogLevel, LogLevelColor, LogsMetaItem, LogsModel, LogRow, LogsStream } from 'app/core/logs_model';
  4. import { TimeSeries } from 'app/core/core';
  5. export function getLogLevel(line: string): LogLevel {
  6. if (!line) {
  7. return LogLevel.none;
  8. }
  9. let level: LogLevel;
  10. Object.keys(LogLevel).forEach(key => {
  11. if (!level) {
  12. const regexp = new RegExp(`\\b${key}\\b`, 'i');
  13. if (regexp.test(line)) {
  14. level = LogLevel[key];
  15. }
  16. }
  17. });
  18. if (!level) {
  19. level = LogLevel.none;
  20. }
  21. return level;
  22. }
  23. const labelRegexp = /\b(\w+)(!?=~?)("[^"\n]*?")/g;
  24. export function parseLabels(labels: string): { [key: string]: string } {
  25. const labelsByKey = {};
  26. labels.replace(labelRegexp, (_, key, operator, value) => {
  27. labelsByKey[key] = value;
  28. return '';
  29. });
  30. return labelsByKey;
  31. }
  32. export function findCommonLabels(labelsSets: any[]) {
  33. return labelsSets.reduce((acc, labels) => {
  34. if (!labels) {
  35. throw new Error('Need parsed labels to find common labels.');
  36. }
  37. if (!acc) {
  38. // Initial set
  39. acc = { ...labels };
  40. } else {
  41. // Remove incoming labels that are missing or not matching in value
  42. Object.keys(labels).forEach(key => {
  43. if (acc[key] === undefined || acc[key] !== labels[key]) {
  44. delete acc[key];
  45. }
  46. });
  47. // Remove common labels that are missing from incoming label set
  48. Object.keys(acc).forEach(key => {
  49. if (labels[key] === undefined) {
  50. delete acc[key];
  51. }
  52. });
  53. }
  54. return acc;
  55. }, undefined);
  56. }
  57. export function findUncommonLabels(labels, commonLabels) {
  58. const uncommonLabels = { ...labels };
  59. Object.keys(commonLabels).forEach(key => {
  60. delete uncommonLabels[key];
  61. });
  62. return uncommonLabels;
  63. }
  64. export function formatLabels(labels, defaultValue = '') {
  65. if (!labels || Object.keys(labels).length === 0) {
  66. return defaultValue;
  67. }
  68. const labelKeys = Object.keys(labels).sort();
  69. const cleanSelector = labelKeys.map(key => `${key}=${labels[key]}`).join(', ');
  70. return ['{', cleanSelector, '}'].join('');
  71. }
  72. export function processEntry(entry: { line: string; timestamp: string }, stream): LogRow {
  73. const { line, timestamp } = entry;
  74. const { labels } = stream;
  75. const key = `EK${timestamp}${labels}`;
  76. const time = moment(timestamp);
  77. const timeJs = time.valueOf();
  78. const timeFromNow = time.fromNow();
  79. const timeLocal = time.format('YYYY-MM-DD HH:mm:ss');
  80. const logLevel = getLogLevel(line);
  81. return {
  82. key,
  83. logLevel,
  84. timeFromNow,
  85. timeJs,
  86. timeLocal,
  87. entry: line,
  88. labels: formatLabels(labels),
  89. searchWords: [stream.search],
  90. timestamp: timestamp,
  91. };
  92. }
  93. export function mergeStreams(streams: LogsStream[], limit?: number): LogsModel {
  94. // Find meta data
  95. const commonLabels = findCommonLabels(streams.map(stream => stream.parsedLabels));
  96. const meta: LogsMetaItem[] = [
  97. {
  98. label: 'Common labels',
  99. value: formatLabels(commonLabels),
  100. },
  101. ];
  102. let intervalMs;
  103. // Flatten entries of streams
  104. const combinedEntries: LogRow[] = streams.reduce((acc, stream) => {
  105. // Set interval for graphs
  106. intervalMs = stream.intervalMs;
  107. // Overwrite labels to be only the non-common ones
  108. const labels = formatLabels(findUncommonLabels(stream.parsedLabels, commonLabels));
  109. return [
  110. ...acc,
  111. ...stream.entries.map(entry => ({
  112. ...entry,
  113. labels,
  114. })),
  115. ];
  116. }, []);
  117. // Graph time series by log level
  118. const seriesByLevel = {};
  119. combinedEntries.forEach(entry => {
  120. if (!seriesByLevel[entry.logLevel]) {
  121. seriesByLevel[entry.logLevel] = { lastTs: null, datapoints: [], alias: entry.logLevel };
  122. }
  123. const levelSeries = seriesByLevel[entry.logLevel];
  124. // Bucket to nearest minute
  125. const time = Math.round(entry.timeJs / intervalMs / 10) * intervalMs * 10;
  126. // Entry for time
  127. if (time === levelSeries.lastTs) {
  128. levelSeries.datapoints[levelSeries.datapoints.length - 1][0]++;
  129. } else {
  130. levelSeries.datapoints.push([1, time]);
  131. levelSeries.lastTs = time;
  132. }
  133. });
  134. const series = Object.keys(seriesByLevel).reduce((acc, level, index) => {
  135. if (seriesByLevel[level]) {
  136. const gs = new TimeSeries(seriesByLevel[level]);
  137. gs.setColor(LogLevelColor[level]);
  138. acc.push(gs);
  139. }
  140. return acc;
  141. }, []);
  142. const sortedEntries = _.chain(combinedEntries)
  143. .sortBy('timestamp')
  144. .reverse()
  145. .slice(0, limit || combinedEntries.length)
  146. .value();
  147. meta.push({
  148. label: 'Limit',
  149. value: `${limit} (${sortedEntries.length} returned)`,
  150. });
  151. return { meta, series, rows: sortedEntries };
  152. }
  153. export function processStream(stream: LogsStream, limit?: number, intervalMs?: number): LogsStream {
  154. const sortedEntries: any[] = _.chain(stream.entries)
  155. .map(entry => processEntry(entry, stream))
  156. .sortBy('timestamp')
  157. .reverse()
  158. .slice(0, limit || stream.entries.length)
  159. .value();
  160. return {
  161. ...stream,
  162. intervalMs,
  163. entries: sortedEntries,
  164. parsedLabels: parseLabels(stream.labels),
  165. };
  166. }