mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 04:51:49 +08:00
188 lines
5.6 KiB
Go
188 lines
5.6 KiB
Go
package loganalytics
|
|
|
|
import (
|
|
"compress/flate"
|
|
"compress/gzip"
|
|
"fmt"
|
|
"io"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/andybalholm/brotli"
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/kinds/dataquery"
|
|
)
|
|
|
|
func AddCustomDataLink(frame data.Frame, dataLink data.DataLink, singleField bool) data.Frame {
|
|
for i := range frame.Fields {
|
|
if frame.Fields[i].Config == nil {
|
|
frame.Fields[i].Config = &data.FieldConfig{}
|
|
}
|
|
|
|
frame.Fields[i].Config.Links = append(frame.Fields[i].Config.Links, dataLink)
|
|
|
|
// Queries using the trace viz only need the link added to a single field
|
|
if singleField {
|
|
break
|
|
}
|
|
}
|
|
return frame
|
|
}
|
|
|
|
const SingleField bool = true
|
|
const MultiField bool = false
|
|
|
|
func AddConfigLinks(frame data.Frame, dl string, title *string) data.Frame {
|
|
linkTitle := "View query in Azure Portal"
|
|
if title != nil {
|
|
linkTitle = *title
|
|
}
|
|
|
|
deepLink := data.DataLink{
|
|
Title: linkTitle,
|
|
TargetBlank: true,
|
|
URL: dl,
|
|
}
|
|
|
|
frame = AddCustomDataLink(frame, deepLink, MultiField)
|
|
|
|
return frame
|
|
}
|
|
|
|
// Check whether a query should be handled as basic logs query
|
|
// 1. resource selected is a workspace
|
|
// 2. query is not an alerts query
|
|
// 3. number of selected resources is exactly one
|
|
// 4. the ds toggle is set to true
|
|
func meetsBasicLogsCriteria(resources []string, fromAlert bool, basicLogsEnabled bool) (bool, error) {
|
|
if fromAlert {
|
|
return false, backend.DownstreamError(fmt.Errorf("basic Logs queries cannot be used for alerts"))
|
|
}
|
|
if len(resources) != 1 {
|
|
return false, backend.DownstreamError(fmt.Errorf("basic logs queries cannot be run against multiple resources"))
|
|
}
|
|
|
|
if !strings.Contains(strings.ToLower(resources[0]), "microsoft.operationalinsights/workspaces") {
|
|
return false, backend.DownstreamError(fmt.Errorf("basic logs queries may only be run against Log Analytics workspaces"))
|
|
}
|
|
|
|
if !basicLogsEnabled {
|
|
return false, backend.DownstreamError(fmt.Errorf("basic Logs queries are disabled for this data source"))
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// This function should be part of migration function
|
|
func ParseResultFormat(queryResultFormat *dataquery.ResultFormat, queryType dataquery.AzureQueryType) dataquery.ResultFormat {
|
|
if queryResultFormat != nil && *queryResultFormat != "" {
|
|
return *queryResultFormat
|
|
}
|
|
if queryType == dataquery.AzureQueryTypeLogAnalytics {
|
|
// Default to time series format for logs queries. It was time series before this change
|
|
return dataquery.ResultFormatTimeSeries
|
|
}
|
|
if queryType == dataquery.AzureQueryTypeAzureTraces {
|
|
// Default to table format for traces queries as many traces may be returned
|
|
return dataquery.ResultFormatTable
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func getApiURL(resourceOrWorkspace string, isAppInsightsQuery bool, basicLogsQuery bool) string {
|
|
matchesResourceURI, _ := regexp.MatchString("^/subscriptions/", resourceOrWorkspace)
|
|
|
|
queryOrSearch := "query"
|
|
if basicLogsQuery {
|
|
queryOrSearch = "search"
|
|
}
|
|
|
|
if matchesResourceURI {
|
|
if isAppInsightsQuery {
|
|
componentName := resourceOrWorkspace[strings.LastIndex(resourceOrWorkspace, "/")+1:]
|
|
return fmt.Sprintf("v1/apps/%s/query", componentName)
|
|
}
|
|
return fmt.Sprintf("v1%s/%s", resourceOrWorkspace, queryOrSearch)
|
|
} else {
|
|
return fmt.Sprintf("v1/workspaces/%s/%s", resourceOrWorkspace, queryOrSearch)
|
|
}
|
|
}
|
|
|
|
// Legacy queries only specify a Workspace GUID, which we need to use the old workspace-centric
|
|
// API URL for, and newer queries specifying a resource URI should use resource-centric API.
|
|
// However, legacy workspace queries using a `workspaces()` template variable will be resolved
|
|
// to a resource URI, so they should use the new resource-centric.
|
|
func retrieveResources(query dataquery.AzureLogsQuery) ([]string, string) {
|
|
resources := []string{}
|
|
var resourceOrWorkspace string
|
|
if len(query.Resources) > 0 {
|
|
resources = query.Resources
|
|
resourceOrWorkspace = query.Resources[0]
|
|
} else if query.Resource != nil && *query.Resource != "" {
|
|
resources = []string{*query.Resource}
|
|
resourceOrWorkspace = *query.Resource
|
|
} else if query.Workspace != nil {
|
|
resourceOrWorkspace = *query.Workspace
|
|
}
|
|
|
|
return resources, resourceOrWorkspace
|
|
}
|
|
|
|
func ConvertTime(timeStamp string) (time.Time, error) {
|
|
// Convert the timestamp string to an int64
|
|
timestampInt, err := strconv.ParseInt(timeStamp, 10, 64)
|
|
if err != nil {
|
|
// Handle error
|
|
return time.Time{}, err
|
|
}
|
|
|
|
// Convert the Unix timestamp (in milliseconds) to a time.Time
|
|
convTimeStamp := time.Unix(0, timestampInt*int64(time.Millisecond))
|
|
|
|
return convTimeStamp, nil
|
|
}
|
|
|
|
func GetDataVolumeRawQuery(table string) string {
|
|
return fmt.Sprintf("Usage \n| where DataType == \"%s\"\n| where IsBillable == true\n| summarize BillableDataGB = round(sum(Quantity) / 1000, 3)", table)
|
|
}
|
|
|
|
// This function handles various compression mechanisms that may have been used on a response body
|
|
func decode(encoding string, original io.ReadCloser) ([]byte, error) {
|
|
var reader io.Reader
|
|
var err error
|
|
switch encoding {
|
|
case "gzip":
|
|
reader, err = gzip.NewReader(original)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() {
|
|
if err := reader.(io.ReadCloser).Close(); err != nil {
|
|
backend.Logger.Warn("Failed to close reader body", "err", err)
|
|
}
|
|
}()
|
|
case "deflate":
|
|
reader = flate.NewReader(original)
|
|
defer func() {
|
|
if err := reader.(io.ReadCloser).Close(); err != nil {
|
|
backend.Logger.Warn("Failed to close reader body", "err", err)
|
|
}
|
|
}()
|
|
case "br":
|
|
reader = brotli.NewReader(original)
|
|
case "":
|
|
reader = original
|
|
default:
|
|
return nil, fmt.Errorf("unexpected encoding type %v", err)
|
|
}
|
|
|
|
body, err := io.ReadAll(reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return body, nil
|
|
}
|