mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Azure Application Insights Analytics is no longer accessed by the edit button from within the Application Insights service. Instead, there is now an Insights Analytics option in the Service drop down. Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
233 lines
6.2 KiB
Go
233 lines
6.2 KiB
Go
package azuremonitor
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
"github.com/grafana/grafana/pkg/api/pluginproxy"
|
|
"github.com/grafana/grafana/pkg/models"
|
|
"github.com/grafana/grafana/pkg/plugins"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
"github.com/grafana/grafana/pkg/tsdb"
|
|
"github.com/grafana/grafana/pkg/util/errutil"
|
|
"github.com/opentracing/opentracing-go"
|
|
"golang.org/x/net/context/ctxhttp"
|
|
)
|
|
|
|
type InsightsAnalyticsDatasource struct {
|
|
httpClient *http.Client
|
|
dsInfo *models.DataSource
|
|
}
|
|
|
|
type InsightsAnalyticsQuery struct {
|
|
RefID string
|
|
|
|
RawQuery string
|
|
InterpolatedQuery string
|
|
|
|
ResultFormat string
|
|
|
|
Params url.Values
|
|
Target string
|
|
}
|
|
|
|
func (e *InsightsAnalyticsDatasource) executeTimeSeriesQuery(ctx context.Context, originalQueries []*tsdb.Query, timeRange *tsdb.TimeRange) (*tsdb.Response, error) {
|
|
result := &tsdb.Response{
|
|
Results: map[string]*tsdb.QueryResult{},
|
|
}
|
|
|
|
queries, err := e.buildQueries(originalQueries, timeRange)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, query := range queries {
|
|
result.Results[query.RefID] = e.executeQuery(ctx, query)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (e *InsightsAnalyticsDatasource) buildQueries(queries []*tsdb.Query, timeRange *tsdb.TimeRange) ([]*InsightsAnalyticsQuery, error) {
|
|
iaQueries := []*InsightsAnalyticsQuery{}
|
|
|
|
for _, query := range queries {
|
|
queryBytes, err := query.Model.Encode()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to re-encode the Azure Application Insights Analytics query into JSON: %w", err)
|
|
}
|
|
|
|
qm := InsightsAnalyticsQuery{}
|
|
queryJSONModel := insightsAnalyticsJSONQuery{}
|
|
err = json.Unmarshal(queryBytes, &queryJSONModel)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode the Azure Application Insights Analytics query object from JSON: %w", err)
|
|
}
|
|
|
|
qm.RawQuery = queryJSONModel.InsightsAnalytics.Query
|
|
qm.ResultFormat = queryJSONModel.InsightsAnalytics.ResultFormat
|
|
qm.RefID = query.RefId
|
|
|
|
if qm.RawQuery == "" {
|
|
return nil, fmt.Errorf("query is missing query string property")
|
|
}
|
|
|
|
qm.InterpolatedQuery, err = KqlInterpolate(query, timeRange, qm.RawQuery)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
qm.Params = url.Values{}
|
|
qm.Params.Add("query", qm.InterpolatedQuery)
|
|
|
|
qm.Target = qm.Params.Encode()
|
|
iaQueries = append(iaQueries, &qm)
|
|
|
|
}
|
|
|
|
return iaQueries, nil
|
|
}
|
|
|
|
func (e *InsightsAnalyticsDatasource) executeQuery(ctx context.Context, query *InsightsAnalyticsQuery) *tsdb.QueryResult {
|
|
queryResult := &tsdb.QueryResult{RefId: query.RefID}
|
|
|
|
queryResultError := func(err error) *tsdb.QueryResult {
|
|
queryResult.Error = err
|
|
return queryResult
|
|
}
|
|
|
|
req, err := e.createRequest(ctx, e.dsInfo)
|
|
if err != nil {
|
|
queryResultError(err)
|
|
}
|
|
req.URL.Path = path.Join(req.URL.Path, "query")
|
|
req.URL.RawQuery = query.Params.Encode()
|
|
|
|
span, ctx := opentracing.StartSpanFromContext(ctx, "application insights analytics query")
|
|
span.SetTag("target", query.Target)
|
|
span.SetTag("datasource_id", e.dsInfo.Id)
|
|
span.SetTag("org_id", e.dsInfo.OrgId)
|
|
|
|
defer span.Finish()
|
|
|
|
err = opentracing.GlobalTracer().Inject(
|
|
span.Context(),
|
|
opentracing.HTTPHeaders,
|
|
opentracing.HTTPHeadersCarrier(req.Header))
|
|
|
|
if err != nil {
|
|
azlog.Warn("failed to inject global tracer")
|
|
}
|
|
|
|
azlog.Debug("ApplicationInsights", "Request URL", req.URL.String())
|
|
res, err := ctxhttp.Do(ctx, e.httpClient, req)
|
|
if err != nil {
|
|
queryResultError(err)
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(res.Body)
|
|
defer res.Body.Close()
|
|
if err != nil {
|
|
queryResultError(err)
|
|
}
|
|
|
|
if res.StatusCode/100 != 2 {
|
|
azlog.Debug("Request failed", "status", res.Status, "body", string(body))
|
|
queryResultError(fmt.Errorf("Request failed status: %v", res.Status))
|
|
}
|
|
var logResponse AzureLogAnalyticsResponse
|
|
d := json.NewDecoder(bytes.NewReader(body))
|
|
d.UseNumber()
|
|
err = d.Decode(&logResponse)
|
|
if err != nil {
|
|
queryResultError(err)
|
|
}
|
|
|
|
t, err := logResponse.GetPrimaryResultTable()
|
|
if err != nil {
|
|
queryResultError(err)
|
|
}
|
|
|
|
frame, err := LogTableToFrame(t)
|
|
if err != nil {
|
|
return queryResultError(err)
|
|
}
|
|
|
|
if query.ResultFormat == "time_series" {
|
|
tsSchema := frame.TimeSeriesSchema()
|
|
if tsSchema.Type == data.TimeSeriesTypeLong {
|
|
wideFrame, err := data.LongToWide(frame, &data.FillMissing{})
|
|
if err == nil {
|
|
frame = wideFrame
|
|
} else {
|
|
frame.AppendNotices(data.Notice{Severity: data.NoticeSeverityWarning, Text: "could not convert frame to time series, returning raw table: " + err.Error()})
|
|
}
|
|
}
|
|
}
|
|
frames := data.Frames{frame}
|
|
queryResult.Dataframes = tsdb.NewDecodedDataFrames(frames)
|
|
|
|
return queryResult
|
|
}
|
|
|
|
func (e *InsightsAnalyticsDatasource) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) {
|
|
// find plugin
|
|
plugin, ok := plugins.DataSources[dsInfo.Type]
|
|
if !ok {
|
|
return nil, errors.New("Unable to find datasource plugin Azure Application Insights")
|
|
}
|
|
|
|
cloudName := dsInfo.JsonData.Get("cloudName").MustString("azuremonitor")
|
|
appInsightsRoute, pluginRouteName, err := e.getPluginRoute(plugin, cloudName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
appInsightsAppID := dsInfo.JsonData.Get("appInsightsAppId").MustString()
|
|
proxyPass := fmt.Sprintf("%s/v1/apps/%s", pluginRouteName, appInsightsAppID)
|
|
|
|
u, err := url.Parse(dsInfo.Url)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to parse url for Application Insights Analytics datasource: %w", err)
|
|
}
|
|
u.Path = path.Join(u.Path, fmt.Sprintf("/v1/apps/%s", appInsightsAppID))
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
if err != nil {
|
|
azlog.Debug("Failed to create request", "error", err)
|
|
return nil, errutil.Wrap("Failed to create request", err)
|
|
}
|
|
|
|
req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))
|
|
|
|
pluginproxy.ApplyRoute(ctx, req, proxyPass, appInsightsRoute, dsInfo)
|
|
|
|
return req, nil
|
|
}
|
|
|
|
func (e *InsightsAnalyticsDatasource) getPluginRoute(plugin *plugins.DataSourcePlugin, cloudName string) (*plugins.AppPluginRoute, string, error) {
|
|
pluginRouteName := "appinsights"
|
|
|
|
if cloudName == "chinaazuremonitor" {
|
|
pluginRouteName = "chinaappinsights"
|
|
}
|
|
|
|
var pluginRoute *plugins.AppPluginRoute
|
|
|
|
for _, route := range plugin.Routes {
|
|
if route.Path == pluginRouteName {
|
|
pluginRoute = route
|
|
break
|
|
}
|
|
}
|
|
|
|
return pluginRoute, pluginRouteName, nil
|
|
}
|