Like Prometheus, but for logs.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
loki/pkg/logcli/query/query.go

171 lines
4.3 KiB

package query
import (
"fmt"
"log"
"sort"
"strings"
"time"
"github.com/fatih/color"
"github.com/grafana/loki/pkg/logcli/client"
"github.com/grafana/loki/pkg/logcli/output"
"github.com/grafana/loki/pkg/loghttp"
"github.com/grafana/loki/pkg/logproto"
"github.com/grafana/loki/pkg/logql"
json "github.com/json-iterator/go"
"github.com/prometheus/prometheus/promql"
)
type streamEntryPair struct {
entry loghttp.Entry
labels loghttp.LabelSet
}
// Query contains all necessary fields to execute instant and range queries and print the results.
type Query struct {
QueryString string
Start time.Time
End time.Time
Limit int
Forward bool
Step time.Duration
Quiet bool
NoLabels bool
IgnoreLabelsKey []string
ShowLabelsKey []string
FixedLabelsLen int
}
// DoQuery executes the query and prints out the results
func (q *Query) DoQuery(c *client.Client, out output.LogOutput) {
d := q.resultsDirection()
var resp *loghttp.QueryResponse
var err error
if q.isInstant() {
resp, err = c.Query(q.QueryString, q.Limit, q.Start, d, q.Quiet)
} else {
resp, err = c.QueryRange(q.QueryString, q.Limit, q.Start, q.End, d, q.Step, q.Quiet)
}
if err != nil {
log.Fatalf("Query failed: %+v", err)
}
switch resp.Data.ResultType {
case logql.ValueTypeStreams:
streams := resp.Data.Result.(loghttp.Streams)
q.printStream(streams, out)
case promql.ValueTypeMatrix:
matrix := resp.Data.Result.(loghttp.Matrix)
q.printMatrix(matrix)
case promql.ValueTypeVector:
vector := resp.Data.Result.(loghttp.Vector)
q.printVector(vector)
default:
log.Fatalf("Unable to print unsupported type: %v", resp.Data.ResultType)
}
}
// SetInstant makes the Query an instant type
func (q *Query) SetInstant(time time.Time) {
q.Start = time
q.End = time
}
func (q *Query) isInstant() bool {
return q.Start == q.End
}
func (q *Query) printStream(streams loghttp.Streams, out output.LogOutput) {
common := commonLabels(streams)
// Remove the labels we want to show from common
if len(q.ShowLabelsKey) > 0 {
common = matchLabels(false, common, q.ShowLabelsKey)
}
if len(common) > 0 && !q.Quiet {
log.Println("Common labels:", color.RedString(common.String()))
}
if len(q.IgnoreLabelsKey) > 0 && !q.Quiet {
log.Println("Ignoring labels key:", color.RedString(strings.Join(q.IgnoreLabelsKey, ",")))
}
// Remove ignored and common labels from the cached labels and
// calculate the max labels length
maxLabelsLen := q.FixedLabelsLen
for i, s := range streams {
// Remove common labels
ls := subtract(s.Labels, common)
// Remove ignored labels
if len(q.IgnoreLabelsKey) > 0 {
ls = matchLabels(false, ls, q.IgnoreLabelsKey)
}
// Overwrite existing Labels
streams[i].Labels = ls
// Update max labels length
len := len(ls.String())
if maxLabelsLen < len {
maxLabelsLen = len
}
}
// sort and display entries
allEntries := make([]streamEntryPair, 0)
for _, s := range streams {
for _, e := range s.Entries {
allEntries = append(allEntries, streamEntryPair{
entry: e,
labels: s.Labels,
})
}
}
if q.Forward {
sort.Slice(allEntries, func(i, j int) bool { return allEntries[i].entry.Timestamp.Before(allEntries[j].entry.Timestamp) })
} else {
sort.Slice(allEntries, func(i, j int) bool { return allEntries[i].entry.Timestamp.After(allEntries[j].entry.Timestamp) })
}
for _, e := range allEntries {
fmt.Println(out.Format(e.entry.Timestamp, e.labels, maxLabelsLen, e.entry.Line))
}
}
func (q *Query) printMatrix(matrix loghttp.Matrix) {
// yes we are effectively unmarshalling and then immediately marshalling this object back to json. we are doing this b/c
// it gives us more flexibility with regard to output types in the future. initially we are supporting just formatted json but eventually
// we might add output options such as render to an image file on disk
bytes, err := json.MarshalIndent(matrix, "", " ")
if err != nil {
log.Fatalf("Error marshalling matrix: %v", err)
}
fmt.Print(string(bytes))
}
func (q *Query) printVector(vector loghttp.Vector) {
bytes, err := json.MarshalIndent(vector, "", " ")
if err != nil {
log.Fatalf("Error marshalling vector: %v", err)
}
fmt.Print(string(bytes))
}
func (q *Query) resultsDirection() logproto.Direction {
if q.Forward {
return logproto.FORWARD
}
return logproto.BACKWARD
}