浏览代码

Made really good progress on loki support in dashboards

Torkel Ödegaard 6 年之前
父节点
当前提交
740e1a0540

+ 2 - 3
public/app/core/logs_model.ts

@@ -1,7 +1,6 @@
 import _ from 'lodash';
-import { colors } from '@grafana/ui';
 
-import { TimeSeries } from 'app/core/core';
+import { colors, TimeSeries } from '@grafana/ui';
 import { getThemeColor } from 'app/core/utils/colors';
 
 /**
@@ -341,6 +340,6 @@ export function makeSeriesForLogs(rows: LogRowModel[], intervalMs: number): Time
       return a[1] - b[1];
     });
 
-    return new TimeSeries(series);
+    return { datapoints: series.datapoints, target: series.alias, color: series.color };
   });
 }

+ 1 - 1
public/app/features/explore/Explore.tsx

@@ -216,7 +216,7 @@ export class Explore extends React.PureComponent<ExploreProps> {
                       {showingStartPage && <StartPage onClickExample={this.onClickExample} />}
                       {!showingStartPage && (
                         <>
-                          {supportsGraph && <GraphContainer exploreId={exploreId} />}
+                          {supportsGraph && !supportsLogs && <GraphContainer exploreId={exploreId} />}
                           {supportsTable && <TableContainer exploreId={exploreId} onClickCell={this.onClickLabel} />}
                           {supportsLogs && (
                             <LogsContainer

+ 4 - 1
public/app/features/explore/Logs.tsx

@@ -3,6 +3,8 @@ import React, { PureComponent } from 'react';
 
 import * as rangeUtil from 'app/core/utils/rangeutil';
 import { RawTimeRange, Switch } from '@grafana/ui';
+import TimeSeries from 'app/core/time_series2';
+
 import {
   LogsDedupDescription,
   LogsDedupStrategy,
@@ -205,12 +207,13 @@ export default class Logs extends PureComponent<Props, State> {
 
     // React profiler becomes unusable if we pass all rows to all rows and their labels, using getter instead
     const getRows = () => processedRows;
+    const timeSeries = data.series.map(series => new TimeSeries(series));
 
     return (
       <div className="logs-panel">
         <div className="logs-panel-graph">
           <Graph
-            data={data.series}
+            data={timeSeries}
             height="100px"
             range={range}
             id={`explore-logs-graph-${exploreId}`}

+ 41 - 2
public/app/plugins/datasource/loki/datasource.test.ts

@@ -7,6 +7,17 @@ describe('LokiDatasource', () => {
     url: 'myloggingurl',
   };
 
+  const testResp = {
+    data: {
+      streams: [
+        {
+          entries: [{ ts: '2019-02-01T10:27:37.498180581Z', line: 'hello' }],
+          labels: '{}',
+        },
+      ],
+    },
+  };
+
   describe('when querying', () => {
     const backendSrvMock = { datasourceRequest: jest.fn() };
 
@@ -17,7 +28,7 @@ describe('LokiDatasource', () => {
 
     test('should use default max lines when no limit given', () => {
       const ds = new LokiDatasource(instanceSettings, backendSrvMock, templateSrvMock);
-      backendSrvMock.datasourceRequest = jest.fn();
+      backendSrvMock.datasourceRequest = jest.fn(() => Promise.resolve(testResp));
       const options = getQueryOptions<LokiQuery>({ targets: [{ expr: 'foo', refId: 'B' }] });
 
       ds.query(options);
@@ -30,7 +41,7 @@ describe('LokiDatasource', () => {
       const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
       const customSettings = { ...instanceSettings, jsonData: customData };
       const ds = new LokiDatasource(customSettings, backendSrvMock, templateSrvMock);
-      backendSrvMock.datasourceRequest = jest.fn();
+      backendSrvMock.datasourceRequest = jest.fn(() => Promise.resolve(testResp));
 
       const options = getQueryOptions<LokiQuery>({ targets: [{ expr: 'foo', refId: 'B' }] });
       ds.query(options);
@@ -38,6 +49,34 @@ describe('LokiDatasource', () => {
       expect(backendSrvMock.datasourceRequest.mock.calls.length).toBe(1);
       expect(backendSrvMock.datasourceRequest.mock.calls[0][0].url).toContain('limit=20');
     });
+
+    test('should return log streams when resultFormat is undefined', async done => {
+      const ds = new LokiDatasource(instanceSettings, backendSrvMock, templateSrvMock);
+      backendSrvMock.datasourceRequest = jest.fn(() => Promise.resolve(testResp));
+
+      const options = getQueryOptions<LokiQuery>({
+        targets: [{ expr: 'foo', refId: 'B' }],
+      });
+
+      const res = await ds.query(options);
+
+      expect(res.data[0].entries[0].line).toBe('hello');
+      done();
+    });
+
+    test('should return time series when resultFormat is time_series', async done => {
+      const ds = new LokiDatasource(instanceSettings, backendSrvMock, templateSrvMock);
+      backendSrvMock.datasourceRequest = jest.fn(() => Promise.resolve(testResp));
+
+      const options = getQueryOptions<LokiQuery>({
+        targets: [{ expr: 'foo', refId: 'B', resultFormat: 'time_series' }],
+      });
+
+      const res = await ds.query(options);
+
+      expect(res.data[0].datapoints).toBeDefined();
+      done();
+    });
   });
 
   describe('when performing testDataSource', () => {

+ 52 - 42
public/app/plugins/datasource/loki/datasource.ts

@@ -73,10 +73,11 @@ export class LokiDatasource {
     };
   }
 
-  query(options: DataQueryOptions<LokiQuery>): Promise<{ data: LogsStream[] }> {
+  async query(options: DataQueryOptions<LokiQuery>) {
     const queryTargets = options.targets
-      .filter(target => target.expr)
+      .filter(target => target.expr && !target.hide)
       .map(target => this.prepareQueryTarget(target, options));
+
     if (queryTargets.length === 0) {
       return Promise.resolve({ data: [] });
     }
@@ -84,20 +85,29 @@ export class LokiDatasource {
     const queries = queryTargets.map(target => this._request('/api/prom/query', target));
 
     return Promise.all(queries).then((results: any[]) => {
-      // Flatten streams from multiple queries
-      const allStreams: LogsStream[] = results.reduce((acc, response, i) => {
-        if (!response) {
-          return acc;
+      const allStreams: LogsStream[] = [];
+
+      for (let i = 0; i < results.length; i++) {
+        const result = results[i];
+        const query = queryTargets[i];
+
+        // add search term to stream & add to array
+        if (result.data)  {
+          for (const stream of (result.data.streams || [])) {
+            stream.search = query.regexp;
+            allStreams.push(stream);
+          }
         }
-        const streams: LogsStream[] = response.data.streams || [];
-        // Inject search for match highlighting
-        const search: string = queryTargets[i].regexp;
-        streams.forEach(s => {
-          s.search = search;
-        });
-        return [...acc, ...streams];
-      }, []);
-      return { data: allStreams };
+      }
+
+      // check resultType
+      if (options.targets[0].resultFormat === 'time_series') {
+        const logs = mergeStreamsToLogs(allStreams, this.maxLines);
+        logs.series = makeSeriesForLogs(logs.rows, options.intervalMs);
+        return { data: logs.series };
+      } else {
+        return { data: allStreams };
+      }
     });
   }
 
@@ -142,35 +152,35 @@ export class LokiDatasource {
 
   testDatasource() {
     return this._request('/api/prom/label')
-      .then(res => {
-        if (res && res.data && res.data.values && res.data.values.length > 0) {
-          return { status: 'success', message: 'Data source connected and labels found.' };
-        }
-        return {
-          status: 'error',
-          message:
-            'Data source connected, but no labels received. Verify that Loki and Promtail is configured properly.',
-        };
-      })
-      .catch(err => {
-        let message = 'Loki: ';
-        if (err.statusText) {
-          message += err.statusText;
-        } else {
-          message += 'Cannot connect to Loki';
-        }
+    .then(res => {
+      if (res && res.data && res.data.values && res.data.values.length > 0) {
+        return { status: 'success', message: 'Data source connected and labels found.' };
+      }
+      return {
+        status: 'error',
+        message:
+          'Data source connected, but no labels received. Verify that Loki and Promtail is configured properly.',
+      };
+    })
+    .catch(err => {
+      let message = 'Loki: ';
+      if (err.statusText) {
+        message += err.statusText;
+      } else {
+        message += 'Cannot connect to Loki';
+      }
 
-        if (err.status) {
-          message += `. ${err.status}`;
-        }
+      if (err.status) {
+        message += `. ${err.status}`;
+      }
 
-        if (err.data && err.data.message) {
-          message += `. ${err.data.message}`;
-        } else if (err.data) {
-          message += `. ${err.data}`;
-        }
-        return { status: 'error', message: message };
-      });
+      if (err.data && err.data.message) {
+        message += `. ${err.data.message}`;
+      } else if (err.data) {
+        message += `. ${err.data}`;
+      }
+      return { status: 'error', message: message };
+    });
   }
 }