result_transformer.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. import _ from 'lodash';
  2. import moment from 'moment';
  3. import {
  4. LogLevel,
  5. LogsMetaItem,
  6. LogsModel,
  7. LogRow,
  8. LogsStream,
  9. LogsStreamEntry,
  10. LogsStreamLabels,
  11. LogsMetaKind,
  12. } from 'app/core/logs_model';
  13. import { DEFAULT_LIMIT } from './datasource';
  14. /**
  15. * Returns the log level of a log line.
  16. * Parse the line for level words. If no level is found, it returns `LogLevel.unknown`.
  17. *
  18. * Example: `getLogLevel('WARN 1999-12-31 this is great') // LogLevel.warn`
  19. */
  20. export function getLogLevel(line: string): LogLevel {
  21. if (!line) {
  22. return LogLevel.unkown;
  23. }
  24. let level: LogLevel;
  25. Object.keys(LogLevel).forEach(key => {
  26. if (!level) {
  27. const regexp = new RegExp(`\\b${key}\\b`, 'i');
  28. if (regexp.test(line)) {
  29. level = LogLevel[key];
  30. }
  31. }
  32. });
  33. if (!level) {
  34. level = LogLevel.unkown;
  35. }
  36. return level;
  37. }
  38. /**
  39. * Regexp to extract Prometheus-style labels
  40. */
  41. const labelRegexp = /\b(\w+)(!?=~?)"([^"\n]*?)"/g;
  42. /**
  43. * Returns a map of label keys to value from an input selector string.
  44. *
  45. * Example: `parseLabels('{job="foo", instance="bar"}) // {job: "foo", instance: "bar"}`
  46. */
  47. export function parseLabels(labels: string): LogsStreamLabels {
  48. const labelsByKey: LogsStreamLabels = {};
  49. labels.replace(labelRegexp, (_, key, operator, value) => {
  50. labelsByKey[key] = value;
  51. return '';
  52. });
  53. return labelsByKey;
  54. }
  55. /**
  56. * Returns a map labels that are common to the given label sets.
  57. */
  58. export function findCommonLabels(labelsSets: LogsStreamLabels[]): LogsStreamLabels {
  59. return labelsSets.reduce((acc, labels) => {
  60. if (!labels) {
  61. throw new Error('Need parsed labels to find common labels.');
  62. }
  63. if (!acc) {
  64. // Initial set
  65. acc = { ...labels };
  66. } else {
  67. // Remove incoming labels that are missing or not matching in value
  68. Object.keys(labels).forEach(key => {
  69. if (acc[key] === undefined || acc[key] !== labels[key]) {
  70. delete acc[key];
  71. }
  72. });
  73. // Remove common labels that are missing from incoming label set
  74. Object.keys(acc).forEach(key => {
  75. if (labels[key] === undefined) {
  76. delete acc[key];
  77. }
  78. });
  79. }
  80. return acc;
  81. }, undefined);
  82. }
  83. /**
  84. * Returns a map of labels that are in `labels`, but not in `commonLabels`.
  85. */
  86. export function findUniqueLabels(labels: LogsStreamLabels, commonLabels: LogsStreamLabels): LogsStreamLabels {
  87. const uncommonLabels: LogsStreamLabels = { ...labels };
  88. Object.keys(commonLabels).forEach(key => {
  89. delete uncommonLabels[key];
  90. });
  91. return uncommonLabels;
  92. }
  93. /**
  94. * Serializes the given labels to a string.
  95. */
  96. export function formatLabels(labels: LogsStreamLabels, defaultValue = ''): string {
  97. if (!labels || Object.keys(labels).length === 0) {
  98. return defaultValue;
  99. }
  100. const labelKeys = Object.keys(labels).sort();
  101. const cleanSelector = labelKeys.map(key => `${key}="${labels[key]}"`).join(', ');
  102. return ['{', cleanSelector, '}'].join('');
  103. }
  104. export function processEntry(
  105. entry: LogsStreamEntry,
  106. labels: string,
  107. parsedLabels: LogsStreamLabels,
  108. uniqueLabels: LogsStreamLabels,
  109. search: string
  110. ): LogRow {
  111. const { line, timestamp } = entry;
  112. // Assumes unique-ness, needs nanosec precision for timestamp
  113. const key = `EK${timestamp}${labels}`;
  114. const time = moment(timestamp);
  115. const timeEpochMs = time.valueOf();
  116. const timeFromNow = time.fromNow();
  117. const timeLocal = time.format('YYYY-MM-DD HH:mm:ss');
  118. const logLevel = getLogLevel(line);
  119. return {
  120. key,
  121. logLevel,
  122. timeFromNow,
  123. timeEpochMs,
  124. timeLocal,
  125. uniqueLabels,
  126. entry: line,
  127. labels: parsedLabels,
  128. searchWords: search ? [search] : [],
  129. timestamp: timestamp,
  130. };
  131. }
  132. export function mergeStreamsToLogs(streams: LogsStream[], limit = DEFAULT_LIMIT): LogsModel {
  133. // Unique model identifier
  134. const id = streams.map(stream => stream.labels).join();
  135. // Find unique labels for each stream
  136. streams = streams.map(stream => ({
  137. ...stream,
  138. parsedLabels: parseLabels(stream.labels),
  139. }));
  140. const commonLabels = findCommonLabels(streams.map(model => model.parsedLabels));
  141. streams = streams.map(stream => ({
  142. ...stream,
  143. uniqueLabels: findUniqueLabels(stream.parsedLabels, commonLabels),
  144. }));
  145. // Merge stream entries into single list of log rows
  146. const sortedRows: LogRow[] = _.chain(streams)
  147. .reduce(
  148. (acc: LogRow[], stream: LogsStream) => [
  149. ...acc,
  150. ...stream.entries.map(entry =>
  151. processEntry(entry, stream.labels, stream.parsedLabels, stream.uniqueLabels, stream.search)
  152. ),
  153. ],
  154. []
  155. )
  156. .sortBy('timestamp')
  157. .reverse()
  158. .value();
  159. // Meta data to display in status
  160. const meta: LogsMetaItem[] = [];
  161. if (_.size(commonLabels) > 0) {
  162. meta.push({
  163. label: 'Common labels',
  164. value: commonLabels,
  165. kind: LogsMetaKind.LabelsMap,
  166. });
  167. }
  168. if (limit) {
  169. meta.push({
  170. label: 'Limit',
  171. value: `${limit} (${sortedRows.length} returned)`,
  172. kind: LogsMetaKind.String,
  173. });
  174. }
  175. return {
  176. id,
  177. meta,
  178. rows: sortedRows,
  179. };
  180. }