logs.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import { countBy, chain, map, escapeRegExp } from 'lodash';
  2. import { LogLevel, LogRowModel, LogLabelStatsModel, LogsParser } from '../types/logs';
  3. import { DataFrame, FieldType } from '../types/index';
  4. import { ArrayVector } from './vector';
  5. const LOGFMT_REGEXP = /(?:^|\s)(\w+)=("[^"]*"|\S+)/;
  6. /**
  7. * Returns the log level of a log line.
  8. * Parse the line for level words. If no level is found, it returns `LogLevel.unknown`.
  9. *
  10. * Example: `getLogLevel('WARN 1999-12-31 this is great') // LogLevel.warn`
  11. */
  12. export function getLogLevel(line: string): LogLevel {
  13. if (!line) {
  14. return LogLevel.unknown;
  15. }
  16. for (const key of Object.keys(LogLevel)) {
  17. const regexp = new RegExp(`\\b${key}\\b`, 'i');
  18. if (regexp.test(line)) {
  19. const level = (LogLevel as any)[key];
  20. if (level) {
  21. return level;
  22. }
  23. }
  24. }
  25. return LogLevel.unknown;
  26. }
  27. export function getLogLevelFromKey(key: string): LogLevel {
  28. const level = (LogLevel as any)[key];
  29. if (level) {
  30. return level;
  31. }
  32. return LogLevel.unknown;
  33. }
  34. export function addLogLevelToSeries(series: DataFrame, lineIndex: number): DataFrame {
  35. const levels = new ArrayVector<LogLevel>();
  36. const lines = series.fields[lineIndex];
  37. for (let i = 0; i < lines.values.length; i++) {
  38. const line = lines.values.get(lineIndex);
  39. levels.buffer.push(getLogLevel(line));
  40. }
  41. return {
  42. ...series, // Keeps Tags, RefID etc
  43. fields: [
  44. ...series.fields,
  45. {
  46. name: 'LogLevel',
  47. type: FieldType.string,
  48. values: levels,
  49. config: {},
  50. },
  51. ],
  52. };
  53. }
  54. export function calculateLogsLabelStats(rows: LogRowModel[], label: string): LogLabelStatsModel[] {
  55. // Consider only rows that have the given label
  56. const rowsWithLabel = rows.filter(row => row.labels[label] !== undefined);
  57. const rowCount = rowsWithLabel.length;
  58. // Get label value counts for eligible rows
  59. const countsByValue = countBy(rowsWithLabel, row => (row as LogRowModel).labels[label]);
  60. const sortedCounts = chain(countsByValue)
  61. .map((count, value) => ({ count, value, proportion: count / rowCount }))
  62. .sortBy('count')
  63. .reverse()
  64. .value();
  65. return sortedCounts;
  66. }
  67. export const LogsParsers: { [name: string]: LogsParser } = {
  68. JSON: {
  69. buildMatcher: label => new RegExp(`(?:{|,)\\s*"${label}"\\s*:\\s*"?([\\d\\.]+|[^"]*)"?`),
  70. getFields: line => {
  71. const fields: string[] = [];
  72. try {
  73. const parsed = JSON.parse(line);
  74. map(parsed, (value, key) => {
  75. const fieldMatcher = new RegExp(`"${key}"\\s*:\\s*"?${escapeRegExp(JSON.stringify(value))}"?`);
  76. const match = line.match(fieldMatcher);
  77. if (match) {
  78. fields.push(match[0]);
  79. }
  80. });
  81. } catch {}
  82. return fields;
  83. },
  84. getLabelFromField: field => (field.match(/^"(\w+)"\s*:/) || [])[1],
  85. getValueFromField: field => (field.match(/:\s*(.*)$/) || [])[1],
  86. test: line => {
  87. try {
  88. return JSON.parse(line);
  89. } catch (error) {}
  90. },
  91. },
  92. logfmt: {
  93. buildMatcher: label => new RegExp(`(?:^|\\s)${label}=("[^"]*"|\\S+)`),
  94. getFields: line => {
  95. const fields: string[] = [];
  96. line.replace(new RegExp(LOGFMT_REGEXP, 'g'), substring => {
  97. fields.push(substring.trim());
  98. return '';
  99. });
  100. return fields;
  101. },
  102. getLabelFromField: field => (field.match(LOGFMT_REGEXP) || [])[1],
  103. getValueFromField: field => (field.match(LOGFMT_REGEXP) || [])[2],
  104. test: line => LOGFMT_REGEXP.test(line),
  105. },
  106. };
  107. export function calculateFieldStats(rows: LogRowModel[], extractor: RegExp): LogLabelStatsModel[] {
  108. // Consider only rows that satisfy the matcher
  109. const rowsWithField = rows.filter(row => extractor.test(row.entry));
  110. const rowCount = rowsWithField.length;
  111. // Get field value counts for eligible rows
  112. const countsByValue = countBy(rowsWithField, r => {
  113. const row: LogRowModel = r;
  114. const match = row.entry.match(extractor);
  115. return match ? match[1] : null;
  116. });
  117. const sortedCounts = chain(countsByValue)
  118. .map((count, value) => ({ count, value, proportion: count / rowCount }))
  119. .sortBy('count')
  120. .reverse()
  121. .value();
  122. return sortedCounts;
  123. }
  124. export function getParser(line: string): LogsParser | undefined {
  125. let parser;
  126. try {
  127. if (LogsParsers.JSON.test(line)) {
  128. parser = LogsParsers.JSON;
  129. }
  130. } catch (error) {}
  131. if (!parser && LogsParsers.logfmt.test(line)) {
  132. parser = LogsParsers.logfmt;
  133. }
  134. return parser;
  135. }