datasource.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import _ from 'lodash';
  2. import * as dateMath from 'app/core/utils/datemath';
  3. import { LogsStream, LogsModel, makeSeriesForLogs } from 'app/core/logs_model';
  4. import { PluginMeta, DataQuery } from '@grafana/ui/src/types';
  5. import { addLabelToSelector } from 'app/plugins/datasource/prometheus/add_label_to_query';
  6. import LanguageProvider from './language_provider';
  7. import { mergeStreamsToLogs } from './result_transformer';
  8. import { formatQuery, parseQuery } from './query_utils';
  9. export const DEFAULT_MAX_LINES = 1000;
  10. const DEFAULT_QUERY_PARAMS = {
  11. direction: 'BACKWARD',
  12. limit: DEFAULT_MAX_LINES,
  13. regexp: '',
  14. query: '',
  15. };
  16. function serializeParams(data: any) {
  17. return Object.keys(data)
  18. .map(k => {
  19. const v = data[k];
  20. return encodeURIComponent(k) + '=' + encodeURIComponent(v);
  21. })
  22. .join('&');
  23. }
  24. export default class LokiDatasource {
  25. languageProvider: LanguageProvider;
  26. maxLines: number;
  27. /** @ngInject */
  28. constructor(private instanceSettings, private backendSrv, private templateSrv) {
  29. this.languageProvider = new LanguageProvider(this);
  30. const settingsData = instanceSettings.jsonData || {};
  31. this.maxLines = parseInt(settingsData.maxLines, 10) || DEFAULT_MAX_LINES;
  32. }
  33. _request(apiUrl: string, data?, options?: any) {
  34. const baseUrl = this.instanceSettings.url;
  35. const params = data ? serializeParams(data) : '';
  36. const url = `${baseUrl}${apiUrl}?${params}`;
  37. const req = {
  38. ...options,
  39. url,
  40. };
  41. return this.backendSrv.datasourceRequest(req);
  42. }
  43. mergeStreams(streams: LogsStream[], intervalMs: number): LogsModel {
  44. const logs = mergeStreamsToLogs(streams, this.maxLines);
  45. logs.series = makeSeriesForLogs(logs.rows, intervalMs);
  46. return logs;
  47. }
  48. prepareQueryTarget(target, options) {
  49. const interpolated = this.templateSrv.replace(target.expr);
  50. const start = this.getTime(options.range.from, false);
  51. const end = this.getTime(options.range.to, true);
  52. return {
  53. ...DEFAULT_QUERY_PARAMS,
  54. ...parseQuery(interpolated),
  55. start,
  56. end,
  57. limit: this.maxLines,
  58. };
  59. }
  60. query(options): Promise<{ data: LogsStream[] }> {
  61. const queryTargets = options.targets
  62. .filter(target => target.expr)
  63. .map(target => this.prepareQueryTarget(target, options));
  64. if (queryTargets.length === 0) {
  65. return Promise.resolve({ data: [] });
  66. }
  67. const queries = queryTargets.map(target => this._request('/api/prom/query', target));
  68. return Promise.all(queries).then((results: any[]) => {
  69. // Flatten streams from multiple queries
  70. const allStreams: LogsStream[] = results.reduce((acc, response, i) => {
  71. if (!response) {
  72. return acc;
  73. }
  74. const streams: LogsStream[] = response.data.streams || [];
  75. // Inject search for match highlighting
  76. const search: string = queryTargets[i].regexp;
  77. streams.forEach(s => {
  78. s.search = search;
  79. });
  80. return [...acc, ...streams];
  81. }, []);
  82. return { data: allStreams };
  83. });
  84. }
  85. async importQueries(queries: DataQuery[], originMeta: PluginMeta): Promise<DataQuery[]> {
  86. return this.languageProvider.importQueries(queries, originMeta.id);
  87. }
  88. metadataRequest(url) {
  89. // HACK to get label values for {job=|}, will be replaced when implementing LokiQueryField
  90. const apiUrl = url.replace('v1', 'prom');
  91. return this._request(apiUrl, { silent: true }).then(res => {
  92. const data = { data: { data: res.data.values || [] } };
  93. return data;
  94. });
  95. }
  96. modifyQuery(query: DataQuery, action: any): DataQuery {
  97. const parsed = parseQuery(query.expr || '');
  98. let selector = parsed.query;
  99. switch (action.type) {
  100. case 'ADD_FILTER': {
  101. selector = addLabelToSelector(selector, action.key, action.value);
  102. break;
  103. }
  104. default:
  105. break;
  106. }
  107. const expression = formatQuery(selector, parsed.regexp);
  108. return { ...query, expr: expression };
  109. }
  110. getHighlighterExpression(query: DataQuery): string {
  111. return parseQuery(query.expr).regexp;
  112. }
  113. getTime(date, roundUp) {
  114. if (_.isString(date)) {
  115. date = dateMath.parse(date, roundUp);
  116. }
  117. return Math.ceil(date.valueOf() * 1e6);
  118. }
  119. testDatasource() {
  120. return this._request('/api/prom/label')
  121. .then(res => {
  122. if (res && res.data && res.data.values && res.data.values.length > 0) {
  123. return { status: 'success', message: 'Data source connected and labels found.' };
  124. }
  125. return {
  126. status: 'error',
  127. message:
  128. 'Data source connected, but no labels received. Verify that Loki and Promtail is configured properly.',
  129. };
  130. })
  131. .catch(err => {
  132. let message = 'Loki: ';
  133. if (err.statusText) {
  134. message += err.statusText;
  135. } else {
  136. message += 'Cannot connect to Loki';
  137. }
  138. if (err.status) {
  139. message += `. ${err.status}`;
  140. }
  141. if (err.data && err.data.message) {
  142. message += `. ${err.data.message}`;
  143. } else if (err.data) {
  144. message += `. ${err.data}`;
  145. }
  146. return { status: 'error', message: message };
  147. });
  148. }
  149. }