logs_model.test.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. import {
  2. DataFrame,
  3. FieldType,
  4. LogsModel,
  5. LogsMetaKind,
  6. LogsDedupStrategy,
  7. LogLevel,
  8. MutableDataFrame,
  9. toDataFrame,
  10. } from '@grafana/data';
  11. import { dedupLogRows, dataFrameToLogsModel } from '../logs_model';
  12. describe('dedupLogRows()', () => {
  13. test('should return rows as is when dedup is set to none', () => {
  14. const logs = {
  15. rows: [
  16. {
  17. entry: 'WARN test 1.23 on [xxx]',
  18. },
  19. {
  20. entry: 'WARN test 1.23 on [xxx]',
  21. },
  22. ],
  23. };
  24. expect(dedupLogRows(logs as LogsModel, LogsDedupStrategy.none).rows).toMatchObject(logs.rows);
  25. });
  26. test('should dedup on exact matches', () => {
  27. const logs = {
  28. rows: [
  29. {
  30. entry: 'WARN test 1.23 on [xxx]',
  31. },
  32. {
  33. entry: 'WARN test 1.23 on [xxx]',
  34. },
  35. {
  36. entry: 'INFO test 2.44 on [xxx]',
  37. },
  38. {
  39. entry: 'WARN test 1.23 on [xxx]',
  40. },
  41. ],
  42. };
  43. expect(dedupLogRows(logs as LogsModel, LogsDedupStrategy.exact).rows).toEqual([
  44. {
  45. duplicates: 1,
  46. entry: 'WARN test 1.23 on [xxx]',
  47. },
  48. {
  49. duplicates: 0,
  50. entry: 'INFO test 2.44 on [xxx]',
  51. },
  52. {
  53. duplicates: 0,
  54. entry: 'WARN test 1.23 on [xxx]',
  55. },
  56. ]);
  57. });
  58. test('should dedup on number matches', () => {
  59. const logs = {
  60. rows: [
  61. {
  62. entry: 'WARN test 1.2323423 on [xxx]',
  63. },
  64. {
  65. entry: 'WARN test 1.23 on [xxx]',
  66. },
  67. {
  68. entry: 'INFO test 2.44 on [xxx]',
  69. },
  70. {
  71. entry: 'WARN test 1.23 on [xxx]',
  72. },
  73. ],
  74. };
  75. expect(dedupLogRows(logs as LogsModel, LogsDedupStrategy.numbers).rows).toEqual([
  76. {
  77. duplicates: 1,
  78. entry: 'WARN test 1.2323423 on [xxx]',
  79. },
  80. {
  81. duplicates: 0,
  82. entry: 'INFO test 2.44 on [xxx]',
  83. },
  84. {
  85. duplicates: 0,
  86. entry: 'WARN test 1.23 on [xxx]',
  87. },
  88. ]);
  89. });
  90. test('should dedup on signature matches', () => {
  91. const logs = {
  92. rows: [
  93. {
  94. entry: 'WARN test 1.2323423 on [xxx]',
  95. },
  96. {
  97. entry: 'WARN test 1.23 on [xxx]',
  98. },
  99. {
  100. entry: 'INFO test 2.44 on [xxx]',
  101. },
  102. {
  103. entry: 'WARN test 1.23 on [xxx]',
  104. },
  105. ],
  106. };
  107. expect(dedupLogRows(logs as LogsModel, LogsDedupStrategy.signature).rows).toEqual([
  108. {
  109. duplicates: 3,
  110. entry: 'WARN test 1.2323423 on [xxx]',
  111. },
  112. ]);
  113. });
  114. test('should return to non-deduped state on same log result', () => {
  115. const logs = {
  116. rows: [
  117. {
  118. entry: 'INFO 123',
  119. },
  120. {
  121. entry: 'WARN 123',
  122. },
  123. {
  124. entry: 'WARN 123',
  125. },
  126. ],
  127. };
  128. expect(dedupLogRows(logs as LogsModel, LogsDedupStrategy.exact).rows).toEqual([
  129. {
  130. duplicates: 0,
  131. entry: 'INFO 123',
  132. },
  133. {
  134. duplicates: 1,
  135. entry: 'WARN 123',
  136. },
  137. ]);
  138. expect(dedupLogRows(logs as LogsModel, LogsDedupStrategy.none).rows).toEqual(logs.rows);
  139. });
  140. });
  141. const emptyLogsModel: any = {
  142. hasUniqueLabels: false,
  143. rows: [],
  144. meta: [],
  145. series: [],
  146. };
  147. describe('dataFrameToLogsModel', () => {
  148. it('given empty series should return empty logs model', () => {
  149. expect(dataFrameToLogsModel([] as DataFrame[], 0)).toMatchObject(emptyLogsModel);
  150. });
  151. it('given series without correct series name should return empty logs model', () => {
  152. const series: DataFrame[] = [
  153. toDataFrame({
  154. fields: [],
  155. }),
  156. ];
  157. expect(dataFrameToLogsModel(series, 0)).toMatchObject(emptyLogsModel);
  158. });
  159. it('given series without a time field should return empty logs model', () => {
  160. const series: DataFrame[] = [
  161. new MutableDataFrame({
  162. fields: [
  163. {
  164. name: 'message',
  165. type: FieldType.string,
  166. values: [],
  167. },
  168. ],
  169. }),
  170. ];
  171. expect(dataFrameToLogsModel(series, 0)).toMatchObject(emptyLogsModel);
  172. });
  173. it('given series without a string field should return empty logs model', () => {
  174. const series: DataFrame[] = [
  175. new MutableDataFrame({
  176. fields: [
  177. {
  178. name: 'time',
  179. type: FieldType.time,
  180. values: [],
  181. },
  182. ],
  183. }),
  184. ];
  185. expect(dataFrameToLogsModel(series, 0)).toMatchObject(emptyLogsModel);
  186. });
  187. it('given one series should return expected logs model', () => {
  188. const series: DataFrame[] = [
  189. new MutableDataFrame({
  190. labels: {
  191. filename: '/var/log/grafana/grafana.log',
  192. job: 'grafana',
  193. },
  194. fields: [
  195. {
  196. name: 'time',
  197. type: FieldType.time,
  198. values: ['2019-04-26T09:28:11.352440161Z', '2019-04-26T14:42:50.991981292Z'],
  199. },
  200. {
  201. name: 'message',
  202. type: FieldType.string,
  203. values: [
  204. 't=2019-04-26T11:05:28+0200 lvl=info msg="Initializing DatasourceCacheService" logger=server',
  205. 't=2019-04-26T16:42:50+0200 lvl=eror msg="new token…t unhashed token=56d9fdc5c8b7400bd51b060eea8ca9d7',
  206. ],
  207. },
  208. ],
  209. meta: {
  210. limit: 1000,
  211. },
  212. }),
  213. ];
  214. const logsModel = dataFrameToLogsModel(series, 0);
  215. expect(logsModel.hasUniqueLabels).toBeFalsy();
  216. expect(logsModel.rows).toHaveLength(2);
  217. expect(logsModel.rows).toMatchObject([
  218. {
  219. timestamp: '2019-04-26T09:28:11.352440161Z',
  220. entry: 't=2019-04-26T11:05:28+0200 lvl=info msg="Initializing DatasourceCacheService" logger=server',
  221. labels: { filename: '/var/log/grafana/grafana.log', job: 'grafana' },
  222. logLevel: 'info',
  223. uniqueLabels: {},
  224. },
  225. {
  226. timestamp: '2019-04-26T14:42:50.991981292Z',
  227. entry: 't=2019-04-26T16:42:50+0200 lvl=eror msg="new token…t unhashed token=56d9fdc5c8b7400bd51b060eea8ca9d7',
  228. labels: { filename: '/var/log/grafana/grafana.log', job: 'grafana' },
  229. logLevel: 'error',
  230. uniqueLabels: {},
  231. },
  232. ]);
  233. expect(logsModel.series).toHaveLength(2);
  234. expect(logsModel.meta).toHaveLength(2);
  235. expect(logsModel.meta[0]).toMatchObject({
  236. label: 'Common labels',
  237. value: series[0].labels,
  238. kind: LogsMetaKind.LabelsMap,
  239. });
  240. expect(logsModel.meta[1]).toMatchObject({
  241. label: 'Limit',
  242. value: `1000 (2 returned)`,
  243. kind: LogsMetaKind.String,
  244. });
  245. });
  246. it('given one series without labels should return expected logs model', () => {
  247. const series: DataFrame[] = [
  248. new MutableDataFrame({
  249. fields: [
  250. {
  251. name: 'time',
  252. type: FieldType.time,
  253. values: ['1970-01-01T00:00:01Z'],
  254. },
  255. {
  256. name: 'message',
  257. type: FieldType.string,
  258. values: ['WARN boooo'],
  259. },
  260. {
  261. name: 'level',
  262. type: FieldType.string,
  263. values: ['dbug'],
  264. },
  265. ],
  266. }),
  267. ];
  268. const logsModel = dataFrameToLogsModel(series, 0);
  269. expect(logsModel.rows).toHaveLength(1);
  270. expect(logsModel.rows).toMatchObject([
  271. {
  272. entry: 'WARN boooo',
  273. labels: undefined,
  274. logLevel: LogLevel.debug,
  275. uniqueLabels: {},
  276. },
  277. ]);
  278. });
  279. it('given multiple series with unique times should return expected logs model', () => {
  280. const series: DataFrame[] = [
  281. toDataFrame({
  282. labels: {
  283. foo: 'bar',
  284. baz: '1',
  285. level: 'dbug',
  286. },
  287. fields: [
  288. {
  289. name: 'ts',
  290. type: FieldType.time,
  291. values: ['1970-01-01T00:00:01Z'],
  292. },
  293. {
  294. name: 'line',
  295. type: FieldType.string,
  296. values: ['WARN boooo'],
  297. },
  298. ],
  299. }),
  300. toDataFrame({
  301. name: 'logs',
  302. labels: {
  303. foo: 'bar',
  304. baz: '2',
  305. level: 'err',
  306. },
  307. fields: [
  308. {
  309. name: 'time',
  310. type: FieldType.time,
  311. values: ['1970-01-01T00:00:00Z', '1970-01-01T00:00:02Z'],
  312. },
  313. {
  314. name: 'message',
  315. type: FieldType.string,
  316. values: ['INFO 1', 'INFO 2'],
  317. },
  318. ],
  319. }),
  320. ];
  321. const logsModel = dataFrameToLogsModel(series, 0);
  322. expect(logsModel.hasUniqueLabels).toBeTruthy();
  323. expect(logsModel.rows).toHaveLength(3);
  324. expect(logsModel.rows).toMatchObject([
  325. {
  326. entry: 'INFO 1',
  327. labels: { foo: 'bar', baz: '2' },
  328. logLevel: LogLevel.error,
  329. uniqueLabels: { baz: '2' },
  330. },
  331. {
  332. entry: 'WARN boooo',
  333. labels: { foo: 'bar', baz: '1' },
  334. logLevel: LogLevel.debug,
  335. uniqueLabels: { baz: '1' },
  336. },
  337. {
  338. entry: 'INFO 2',
  339. labels: { foo: 'bar', baz: '2' },
  340. logLevel: LogLevel.error,
  341. uniqueLabels: { baz: '2' },
  342. },
  343. ]);
  344. expect(logsModel.series).toHaveLength(2);
  345. expect(logsModel.meta).toHaveLength(1);
  346. expect(logsModel.meta[0]).toMatchObject({
  347. label: 'Common labels',
  348. value: {
  349. foo: 'bar',
  350. },
  351. kind: LogsMetaKind.LabelsMap,
  352. });
  353. });
  354. //
  355. it('given multiple series with equal times should return expected logs model', () => {
  356. const series: DataFrame[] = [
  357. toDataFrame({
  358. labels: {
  359. foo: 'bar',
  360. baz: '1',
  361. level: 'dbug',
  362. },
  363. fields: [
  364. {
  365. name: 'ts',
  366. type: FieldType.time,
  367. values: ['1970-01-01T00:00:00Z'],
  368. },
  369. {
  370. name: 'line',
  371. type: FieldType.string,
  372. values: ['WARN boooo 1'],
  373. },
  374. ],
  375. }),
  376. toDataFrame({
  377. labels: {
  378. foo: 'bar',
  379. baz: '2',
  380. level: 'dbug',
  381. },
  382. fields: [
  383. {
  384. name: 'ts',
  385. type: FieldType.time,
  386. values: ['1970-01-01T00:00:01Z'],
  387. },
  388. {
  389. name: 'line',
  390. type: FieldType.string,
  391. values: ['WARN boooo 2'],
  392. },
  393. ],
  394. }),
  395. toDataFrame({
  396. name: 'logs',
  397. labels: {
  398. foo: 'bar',
  399. baz: '2',
  400. level: 'err',
  401. },
  402. fields: [
  403. {
  404. name: 'time',
  405. type: FieldType.time,
  406. values: ['1970-01-01T00:00:00Z', '1970-01-01T00:00:01Z'],
  407. },
  408. {
  409. name: 'message',
  410. type: FieldType.string,
  411. values: ['INFO 1', 'INFO 2'],
  412. },
  413. ],
  414. }),
  415. ];
  416. const logsModel = dataFrameToLogsModel(series, 0);
  417. expect(logsModel.hasUniqueLabels).toBeTruthy();
  418. expect(logsModel.rows).toHaveLength(4);
  419. expect(logsModel.rows).toMatchObject([
  420. {
  421. entry: 'WARN boooo 1',
  422. labels: { foo: 'bar', baz: '1' },
  423. logLevel: LogLevel.debug,
  424. uniqueLabels: { baz: '1' },
  425. },
  426. {
  427. entry: 'INFO 1',
  428. labels: { foo: 'bar', baz: '2' },
  429. logLevel: LogLevel.error,
  430. uniqueLabels: { baz: '2' },
  431. },
  432. {
  433. entry: 'WARN boooo 2',
  434. labels: { foo: 'bar', baz: '2' },
  435. logLevel: LogLevel.debug,
  436. uniqueLabels: { baz: '2' },
  437. },
  438. {
  439. entry: 'INFO 2',
  440. labels: { foo: 'bar', baz: '2' },
  441. logLevel: LogLevel.error,
  442. uniqueLabels: { baz: '2' },
  443. },
  444. ]);
  445. });
  446. });