Azure Monitor: Add logs query builder (#99055)

This commit is contained in:
Alyssa (Bull) Joyner
2025-04-02 08:15:25 -06:00
committed by GitHub
parent 44ca402116
commit 3b73ebb210
42 changed files with 3306 additions and 59 deletions
@@ -28,6 +28,18 @@ import (
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/utils"
)
// Returns tables with the `HasData` field set to true
func filterTablesWithData(tables []types.MetadataTable) []types.MetadataTable {
filtered := []types.MetadataTable{}
for _, table := range tables {
if table.HasData {
filtered = append(filtered, table)
}
}
return filtered
}
func (e *AzureLogAnalyticsDatasource) ResourceRequest(rw http.ResponseWriter, req *http.Request, cli *http.Client) (http.ResponseWriter, error) {
if req.URL.Path == "/usage/basiclogs" {
newUrl := &url.URL{
@@ -36,7 +48,57 @@ func (e *AzureLogAnalyticsDatasource) ResourceRequest(rw http.ResponseWriter, re
Path: "/v1/query",
}
return e.GetBasicLogsUsage(req.Context(), newUrl.String(), cli, rw, req.Body)
} else if strings.Contains(req.URL.Path, "/metadata") {
// Add necessary headers
req.Header.Set("Prefer", "metadata-format-v4,exclude-resourcetypes,exclude-customfunctions")
queryParams := req.URL.Query()
// Add necessary query params
queryParams.Add("select", "categories,solutions,tables,workspaces")
req.URL.RawQuery = queryParams.Encode()
resp, err := cli.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to fetch metadata: %w", err)
}
defer func() {
if err := resp.Body.Close(); err != nil {
e.Logger.Warn("Failed to close response body for metadata request", "err", err)
}
}()
encoding := resp.Header.Get("Content-Encoding")
body, err := decode(encoding, resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read metadata response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("metadata API error: %s", string(body))
}
var metadata types.AzureLogAnalyticsMetadata
err = json.Unmarshal(body, &metadata)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal metadata response: %w", err)
}
metadata.Tables = filterTablesWithData(metadata.Tables)
responseBody, err := json.Marshal(metadata)
if err != nil {
return nil, fmt.Errorf("failed to marshal metadata response: %w", err)
}
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK)
_, err = rw.Write(responseBody)
if err != nil {
return nil, fmt.Errorf("failed to write metadata response: %w", err)
}
return rw, nil
}
// Default behavior for other requests
return e.Proxy.Do(rw, req, cli)
}
@@ -1,12 +1,16 @@
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"
@@ -136,3 +140,40 @@ func ConvertTime(timeStamp string) (time.Time, error) {
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
}