result_transformer.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import ansicolor from 'vendor/ansicolor/ansicolor';
  2. import _ from 'lodash';
  3. import moment from 'moment';
  4. import { LogsMetaItem, LogsModel, LogRowModel, LogsStream, LogsStreamEntry, LogsMetaKind } from 'app/core/logs_model';
  5. import { hasAnsiCodes } from 'app/core/utils/text';
  6. import { DEFAULT_MAX_LINES } from './datasource';
  7. import {
  8. parseLabels,
  9. SeriesData,
  10. findUniqueLabels,
  11. Labels,
  12. findCommonLabels,
  13. getLogLevel,
  14. FieldType,
  15. formatLabels,
  16. guessFieldTypeFromSeries,
  17. } from '@grafana/ui';
  18. export function processEntry(
  19. entry: LogsStreamEntry,
  20. labels: string,
  21. parsedLabels: Labels,
  22. uniqueLabels: Labels,
  23. search: string
  24. ): LogRowModel {
  25. const { line } = entry;
  26. const ts = entry.ts || entry.timestamp;
  27. // Assumes unique-ness, needs nanosec precision for timestamp
  28. const key = `EK${ts}${labels}`;
  29. const time = moment(ts);
  30. const timeEpochMs = time.valueOf();
  31. const timeFromNow = time.fromNow();
  32. const timeLocal = time.format('YYYY-MM-DD HH:mm:ss');
  33. const logLevel = getLogLevel(line);
  34. const hasAnsi = hasAnsiCodes(line);
  35. return {
  36. key,
  37. logLevel,
  38. timeFromNow,
  39. timeEpochMs,
  40. timeLocal,
  41. uniqueLabels,
  42. hasAnsi,
  43. entry: hasAnsi ? ansicolor.strip(line) : line,
  44. raw: line,
  45. labels: parsedLabels,
  46. searchWords: search ? [search] : [],
  47. timestamp: ts,
  48. };
  49. }
  50. export function mergeStreamsToLogs(streams: LogsStream[], limit = DEFAULT_MAX_LINES): LogsModel {
  51. // Unique model identifier
  52. const id = streams.map(stream => stream.labels).join();
  53. // Find unique labels for each stream
  54. streams = streams.map(stream => ({
  55. ...stream,
  56. parsedLabels: parseLabels(stream.labels),
  57. }));
  58. const commonLabels = findCommonLabels(streams.map(model => model.parsedLabels));
  59. streams = streams.map(stream => ({
  60. ...stream,
  61. uniqueLabels: findUniqueLabels(stream.parsedLabels, commonLabels),
  62. }));
  63. // Merge stream entries into single list of log rows
  64. const sortedRows: LogRowModel[] = _.chain(streams)
  65. .reduce(
  66. (acc: LogRowModel[], stream: LogsStream) => [
  67. ...acc,
  68. ...stream.entries.map(entry =>
  69. processEntry(entry, stream.labels, stream.parsedLabels, stream.uniqueLabels, stream.search)
  70. ),
  71. ],
  72. []
  73. )
  74. .sortBy('timestamp')
  75. .reverse()
  76. .value();
  77. const hasUniqueLabels = sortedRows && sortedRows.some(row => Object.keys(row.uniqueLabels).length > 0);
  78. // Meta data to display in status
  79. const meta: LogsMetaItem[] = [];
  80. if (_.size(commonLabels) > 0) {
  81. meta.push({
  82. label: 'Common labels',
  83. value: commonLabels,
  84. kind: LogsMetaKind.LabelsMap,
  85. });
  86. }
  87. if (limit) {
  88. meta.push({
  89. label: 'Limit',
  90. value: `${limit} (${sortedRows.length} returned)`,
  91. kind: LogsMetaKind.String,
  92. });
  93. }
  94. return {
  95. id,
  96. hasUniqueLabels,
  97. meta,
  98. rows: sortedRows,
  99. };
  100. }
  101. export function logStreamToSeriesData(stream: LogsStream): SeriesData {
  102. let labels: Labels = stream.parsedLabels;
  103. if (!labels && stream.labels) {
  104. labels = parseLabels(stream.labels);
  105. }
  106. return {
  107. labels,
  108. fields: [{ name: 'ts', type: FieldType.time }, { name: 'line', type: FieldType.string }],
  109. rows: stream.entries.map(entry => {
  110. return [entry.ts || entry.timestamp, entry.line];
  111. }),
  112. };
  113. }
  114. export function seriesDataToLogStream(series: SeriesData): LogsStream {
  115. let timeIndex = -1;
  116. let lineIndex = -1;
  117. for (let i = 0; i < series.fields.length; i++) {
  118. const field = series.fields[i];
  119. const type = field.type || guessFieldTypeFromSeries(series, i);
  120. if (timeIndex < 0 && type === FieldType.time) {
  121. timeIndex = i;
  122. }
  123. if (lineIndex < 0 && type === FieldType.string) {
  124. lineIndex = i;
  125. }
  126. }
  127. if (timeIndex < 0) {
  128. throw new Error('Series does not have a time field');
  129. }
  130. if (lineIndex < 0) {
  131. throw new Error('Series does not have a line field');
  132. }
  133. return {
  134. labels: formatLabels(series.labels),
  135. parsedLabels: series.labels,
  136. entries: series.rows.map(row => {
  137. return {
  138. line: row[lineIndex],
  139. ts: row[timeIndex],
  140. };
  141. }),
  142. };
  143. }