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 }