logs_model.test.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. import {
  2. calculateFieldStats,
  3. calculateLogsLabelStats,
  4. dedupLogRows,
  5. getParser,
  6. LogsDedupStrategy,
  7. LogsModel,
  8. LogsParsers,
  9. } from '../logs_model';
  10. describe('dedupLogRows()', () => {
  11. test('should return rows as is when dedup is set to none', () => {
  12. const logs = {
  13. rows: [
  14. {
  15. entry: 'WARN test 1.23 on [xxx]',
  16. },
  17. {
  18. entry: 'WARN test 1.23 on [xxx]',
  19. },
  20. ],
  21. };
  22. expect(dedupLogRows(logs as LogsModel, LogsDedupStrategy.none).rows).toMatchObject(logs.rows);
  23. });
  24. test('should dedup on exact matches', () => {
  25. const logs = {
  26. rows: [
  27. {
  28. entry: 'WARN test 1.23 on [xxx]',
  29. },
  30. {
  31. entry: 'WARN test 1.23 on [xxx]',
  32. },
  33. {
  34. entry: 'INFO test 2.44 on [xxx]',
  35. },
  36. {
  37. entry: 'WARN test 1.23 on [xxx]',
  38. },
  39. ],
  40. };
  41. expect(dedupLogRows(logs as LogsModel, LogsDedupStrategy.exact).rows).toEqual([
  42. {
  43. duplicates: 1,
  44. entry: 'WARN test 1.23 on [xxx]',
  45. },
  46. {
  47. duplicates: 0,
  48. entry: 'INFO test 2.44 on [xxx]',
  49. },
  50. {
  51. duplicates: 0,
  52. entry: 'WARN test 1.23 on [xxx]',
  53. },
  54. ]);
  55. });
  56. test('should dedup on number matches', () => {
  57. const logs = {
  58. rows: [
  59. {
  60. entry: 'WARN test 1.2323423 on [xxx]',
  61. },
  62. {
  63. entry: 'WARN test 1.23 on [xxx]',
  64. },
  65. {
  66. entry: 'INFO test 2.44 on [xxx]',
  67. },
  68. {
  69. entry: 'WARN test 1.23 on [xxx]',
  70. },
  71. ],
  72. };
  73. expect(dedupLogRows(logs as LogsModel, LogsDedupStrategy.numbers).rows).toEqual([
  74. {
  75. duplicates: 1,
  76. entry: 'WARN test 1.2323423 on [xxx]',
  77. },
  78. {
  79. duplicates: 0,
  80. entry: 'INFO test 2.44 on [xxx]',
  81. },
  82. {
  83. duplicates: 0,
  84. entry: 'WARN test 1.23 on [xxx]',
  85. },
  86. ]);
  87. });
  88. test('should dedup on signature matches', () => {
  89. const logs = {
  90. rows: [
  91. {
  92. entry: 'WARN test 1.2323423 on [xxx]',
  93. },
  94. {
  95. entry: 'WARN test 1.23 on [xxx]',
  96. },
  97. {
  98. entry: 'INFO test 2.44 on [xxx]',
  99. },
  100. {
  101. entry: 'WARN test 1.23 on [xxx]',
  102. },
  103. ],
  104. };
  105. expect(dedupLogRows(logs as LogsModel, LogsDedupStrategy.signature).rows).toEqual([
  106. {
  107. duplicates: 3,
  108. entry: 'WARN test 1.2323423 on [xxx]',
  109. },
  110. ]);
  111. });
  112. });
  113. describe('calculateFieldStats()', () => {
  114. test('should return no stats for empty rows', () => {
  115. expect(calculateFieldStats([], /foo=(.*)/)).toEqual([]);
  116. });
  117. test('should return no stats if extractor does not match', () => {
  118. const rows = [
  119. {
  120. entry: 'foo=bar',
  121. },
  122. ];
  123. expect(calculateFieldStats(rows as any, /baz=(.*)/)).toEqual([]);
  124. });
  125. test('should return stats for found field', () => {
  126. const rows = [
  127. {
  128. entry: 'foo="42 + 1"',
  129. },
  130. {
  131. entry: 'foo=503 baz=foo',
  132. },
  133. {
  134. entry: 'foo="42 + 1"',
  135. },
  136. {
  137. entry: 't=2018-12-05T07:44:59+0000 foo=503',
  138. },
  139. ];
  140. expect(calculateFieldStats(rows as any, /foo=("[^"]*"|\S+)/)).toMatchObject([
  141. {
  142. value: '"42 + 1"',
  143. count: 2,
  144. },
  145. {
  146. value: '503',
  147. count: 2,
  148. },
  149. ]);
  150. });
  151. });
  152. describe('calculateLogsLabelStats()', () => {
  153. test('should return no stats for empty rows', () => {
  154. expect(calculateLogsLabelStats([], '')).toEqual([]);
  155. });
  156. test('should return no stats of label is not found', () => {
  157. const rows = [
  158. {
  159. entry: 'foo 1',
  160. labels: {
  161. foo: 'bar',
  162. },
  163. },
  164. ];
  165. expect(calculateLogsLabelStats(rows as any, 'baz')).toEqual([]);
  166. });
  167. test('should return stats for found labels', () => {
  168. const rows = [
  169. {
  170. entry: 'foo 1',
  171. labels: {
  172. foo: 'bar',
  173. },
  174. },
  175. {
  176. entry: 'foo 0',
  177. labels: {
  178. foo: 'xxx',
  179. },
  180. },
  181. {
  182. entry: 'foo 2',
  183. labels: {
  184. foo: 'bar',
  185. },
  186. },
  187. ];
  188. expect(calculateLogsLabelStats(rows as any, 'foo')).toMatchObject([
  189. {
  190. value: 'bar',
  191. count: 2,
  192. },
  193. {
  194. value: 'xxx',
  195. count: 1,
  196. },
  197. ]);
  198. });
  199. });
  200. describe('getParser()', () => {
  201. test('should return no parser on empty line', () => {
  202. expect(getParser('')).toBeUndefined();
  203. });
  204. test('should return no parser on unknown line pattern', () => {
  205. expect(getParser('To Be or not to be')).toBeUndefined();
  206. });
  207. test('should return logfmt parser on key value patterns', () => {
  208. expect(getParser('foo=bar baz="41 + 1')).toEqual(LogsParsers.logfmt);
  209. });
  210. test('should return JSON parser on JSON log lines', () => {
  211. // TODO implement other JSON value types than string
  212. expect(getParser('{"foo": "bar", "baz": "41 + 1"}')).toEqual(LogsParsers.JSON);
  213. });
  214. });
  215. describe('LogsParsers', () => {
  216. describe('logfmt', () => {
  217. const parser = LogsParsers.logfmt;
  218. test('should detect format', () => {
  219. expect(parser.test('foo')).toBeFalsy();
  220. expect(parser.test('foo=bar')).toBeTruthy();
  221. });
  222. test('should return parsed fields', () => {
  223. expect(parser.getFields('foo=bar baz="42 + 1"')).toEqual(['foo=bar', 'baz="42 + 1"']);
  224. });
  225. test('should return label for field', () => {
  226. expect(parser.getLabelFromField('foo=bar')).toBe('foo');
  227. });
  228. test('should return value for field', () => {
  229. expect(parser.getValueFromField('foo=bar')).toBe('bar');
  230. });
  231. test('should build a valid value matcher', () => {
  232. const matcher = parser.buildMatcher('foo');
  233. const match = 'foo=bar'.match(matcher);
  234. expect(match).toBeDefined();
  235. expect(match[1]).toBe('bar');
  236. });
  237. });
  238. describe('JSON', () => {
  239. const parser = LogsParsers.JSON;
  240. test('should detect format', () => {
  241. expect(parser.test('foo')).toBeFalsy();
  242. expect(parser.test('{"foo":"bar"}')).toBeTruthy();
  243. });
  244. test('should return parsed fields', () => {
  245. expect(parser.getFields('{ "foo" : "bar", "baz" : 42 }')).toEqual(['"foo" : "bar"', '"baz" : 42']);
  246. });
  247. test('should return parsed fields for nested quotes', () => {
  248. expect(parser.getFields(`{"foo":"bar: '[value=\\"42\\"]'"}`)).toEqual([`"foo":"bar: '[value=\\"42\\"]'"`]);
  249. });
  250. test('should return label for field', () => {
  251. expect(parser.getLabelFromField('"foo" : "bar"')).toBe('foo');
  252. });
  253. test('should return value for field', () => {
  254. expect(parser.getValueFromField('"foo" : "bar"')).toBe('"bar"');
  255. expect(parser.getValueFromField('"foo" : 42')).toBe('42');
  256. expect(parser.getValueFromField('"foo" : 42.1')).toBe('42.1');
  257. });
  258. test('should build a valid value matcher for strings', () => {
  259. const matcher = parser.buildMatcher('foo');
  260. const match = '{"foo":"bar"}'.match(matcher);
  261. expect(match).toBeDefined();
  262. expect(match[1]).toBe('bar');
  263. });
  264. test('should build a valid value matcher for integers', () => {
  265. const matcher = parser.buildMatcher('foo');
  266. const match = '{"foo":42.1}'.match(matcher);
  267. expect(match).toBeDefined();
  268. expect(match[1]).toBe('42.1');
  269. });
  270. });
  271. });