Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

impr/logcli: Added label output filters + tests #563

Merged
merged 3 commits into from
May 15, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions cmd/logcli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ var (
username = app.Flag("username", "Username for HTTP basic auth.").Default("").Envar("GRAFANA_USERNAME").String()
password = app.Flag("password", "Password for HTTP basic auth.").Default("").Envar("GRAFANA_PASSWORD").String()

queryCmd = app.Command("query", "Run a LogQL query.")
queryStr = queryCmd.Arg("query", "eg '{foo=\"bar\",baz=\"blip\"}'").Required().String()
regexpStr = queryCmd.Arg("regex", "").String()
limit = queryCmd.Flag("limit", "Limit on number of entries to print.").Default("30").Int()
since = queryCmd.Flag("since", "Lookback window.").Default("1h").Duration()
forward = queryCmd.Flag("forward", "Scan forwards through logs.").Default("false").Bool()
tail = queryCmd.Flag("tail", "Tail the logs").Short('t').Default("false").Bool()
noLabels = queryCmd.Flag("no-labels", "Do not print labels").Default("false").Bool()
queryCmd = app.Command("query", "Run a LogQL query.")
queryStr = queryCmd.Arg("query", "eg '{foo=\"bar\",baz=\"blip\"}'").Required().String()
regexpStr = queryCmd.Arg("regex", "").String()
limit = queryCmd.Flag("limit", "Limit on number of entries to print.").Default("30").Int()
since = queryCmd.Flag("since", "Lookback window.").Default("1h").Duration()
forward = queryCmd.Flag("forward", "Scan forwards through logs.").Default("false").Bool()
tail = queryCmd.Flag("tail", "Tail the logs").Short('t').Default("false").Bool()
noLabels = queryCmd.Flag("no-labels", "Do not print any labels").Default("false").Bool()
ignoreLabelsKey = queryCmd.Flag("no-label", "Do not print labels given the provided key").Strings()
showLabelsKey = queryCmd.Flag("label", "Do print labels given the provided key").Strings()

labelsCmd = app.Command("labels", "Find values for a given label.")
labelName = labelsCmd.Arg("label", "The name of the label.").HintAction(listLabels).String()
Expand Down
93 changes: 55 additions & 38 deletions cmd/logcli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"fmt"
"log"
"sort"
"strings"
"time"

Expand All @@ -21,9 +22,8 @@ func doQuery() {
}

var (
i iter.EntryIterator
common labels.Labels
maxLabelsLen = 100
CyrilPeponnet marked this conversation as resolved.
Show resolved Hide resolved
i iter.EntryIterator
common labels.Labels
)

end := time.Now()
Expand All @@ -43,24 +43,43 @@ func doQuery() {
labelsCache := func(labels string) labels.Labels {
return cache[labels]
}

common = commonLabels(lss)
i = iter.NewQueryResponseIterator(resp, d)

// Remove the labels we want to show from common
if len(*showLabelsKey) > 0 {
common = common.MatchLabels(false, *showLabelsKey...)
}

if len(common) > 0 {
fmt.Println("Common labels:", color.RedString(common.String()))
}

if len(*ignoreLabelsKey) > 0 {
fmt.Println("Ignoring labels key:", color.RedString(strings.Join(*ignoreLabelsKey, ",")))
CyrilPeponnet marked this conversation as resolved.
Show resolved Hide resolved
}

// Get the max size of labels
maxLabelsLen := 0
for _, ls := range cache {
ls = subtract(common, ls)
if len(*ignoreLabelsKey) > 0 {
ls = ls.MatchLabels(false, *ignoreLabelsKey...)
}
len := len(ls.String())
if maxLabelsLen < len {
maxLabelsLen = len
}
}

i = iter.NewQueryResponseIterator(resp, d)

for i.Next() {
ls := labelsCache(i.Labels())
ls = subtract(ls, common)
if len(*ignoreLabelsKey) > 0 {
ls = ls.MatchLabels(false, *ignoreLabelsKey...)
}

labels := ""
if !*noLabels {
Expand Down Expand Up @@ -122,47 +141,45 @@ func commonLabels(lss []labels.Labels) labels.Labels {
return result
}

// intersect two labels set
func intersect(a, b labels.Labels) labels.Labels {
var result labels.Labels
for i, j := 0, 0; i < len(a) && j < len(b); {
k := strings.Compare(a[i].Name, b[j].Name)
switch {
case k == 0:
if a[i].Value == b[j].Value {
result = append(result, a[i])

set := labels.Labels{}
ma := a.Map()
mb := b.Map()

for ka, va := range ma {
if vb, ok := mb[ka]; ok {
if vb == va {
set = append(set, labels.Label{
Name: ka,
Value: va,
})
}
i++
j++
case k < 0:
i++
case k > 0:
j++
}
}
return result
sort.Sort(set)
return set
}

// subtract b from a
// subtract labels set b from labels set a
func subtract(a, b labels.Labels) labels.Labels {
var result labels.Labels
i, j := 0, 0
for i < len(a) && j < len(b) {
k := strings.Compare(a[i].Name, b[j].Name)
if k != 0 || a[i].Value != b[j].Value {
result = append(result, a[i])
}
switch {
case k == 0:
i++
j++
case k < 0:
i++
case k > 0:
j++

set := labels.Labels{}
ma := a.Map()
mb := b.Map()

for ka, va := range ma {
if vb, ok := mb[ka]; ok {
if vb == va {
continue
}
}
set = append(set, labels.Label{
Name: ka,
Value: va,
})
}
for ; i < len(a); i++ {
result = append(result, a[i])
}
return result
sort.Sort(set)
return set
}
130 changes: 130 additions & 0 deletions cmd/logcli/query_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package main

import (
"reflect"
"testing"

"github.com/prometheus/prometheus/pkg/labels"
)

func Test_commonLabels(t *testing.T) {
type args struct {
lss []labels.Labels
}
tests := []struct {
name string
args args
want labels.Labels
}{
{
"Extract common labels source > target",
args{
[]labels.Labels{mustParseLabels(`{foo="bar", bar="foo"}`), mustParseLabels(`{bar="foo", foo="foo", baz="baz"}`)},
},
mustParseLabels(`{bar="foo"}`),
},
{
"Extract common labels source > target",
args{
[]labels.Labels{mustParseLabels(`{foo="bar", bar="foo"}`), mustParseLabels(`{bar="foo", foo="bar", baz="baz"}`)},
},
mustParseLabels(`{foo="bar", bar="foo"}`),
},
{
"Extract common labels source < target",
args{
[]labels.Labels{mustParseLabels(`{foo="bar", bar="foo"}`), mustParseLabels(`{bar="foo"}`)},
},
mustParseLabels(`{bar="foo"}`),
},
{
"Extract common labels source < target no common",
args{
[]labels.Labels{mustParseLabels(`{foo="bar", bar="foo"}`), mustParseLabels(`{fo="bar"}`)},
},
labels.Labels{},
},
{
"Extract common labels source = target no common",
args{
[]labels.Labels{mustParseLabels(`{foo="bar"}`), mustParseLabels(`{fooo="bar"}`)},
},
labels.Labels{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := commonLabels(tt.args.lss); !reflect.DeepEqual(got, tt.want) {
t.Errorf("commonLabels() = %v, want %v", got, tt.want)
}
})
}
}

func Test_subtract(t *testing.T) {
type args struct {
a labels.Labels
b labels.Labels
}
tests := []struct {
name string
args args
want labels.Labels
}{
{
"Subtract labels source > target",
args{
mustParseLabels(`{foo="bar", bar="foo"}`),
mustParseLabels(`{bar="foo", foo="foo", baz="baz"}`),
},
mustParseLabels(`{foo="bar"}`),
},
{
"Subtract labels source < target",
args{
mustParseLabels(`{foo="bar", bar="foo"}`),
mustParseLabels(`{bar="foo"}`),
},
mustParseLabels(`{foo="bar"}`),
},
{
"Subtract labels source < target no sub",
args{
mustParseLabels(`{foo="bar", bar="foo"}`),
mustParseLabels(`{fo="bar"}`),
},
mustParseLabels(`{bar="foo", foo="bar"}`),
},
{
"Subtract labels source = target no sub",
args{
mustParseLabels(`{foo="bar"}`),
mustParseLabels(`{fiz="buz"}`),
},
mustParseLabels(`{foo="bar"}`),
},
{
"Subtract labels source > target no sub",
args{
mustParseLabels(`{foo="bar"}`),
mustParseLabels(`{fiz="buz", foo="baz"}`),
},
mustParseLabels(`{foo="bar"}`),
},
{
"Subtract labels source > target no sub",
args{
mustParseLabels(`{a="b", foo="bar", baz="baz", fizz="fizz"}`),
mustParseLabels(`{foo="bar", baz="baz", buzz="buzz", fizz="fizz"}`),
},
mustParseLabels(`{a="b"}`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := subtract(tt.args.a, tt.args.b); !reflect.DeepEqual(got, tt.want) {
t.Errorf("subtract() = %v, want %v", got, tt.want)
}
})
}
}
32 changes: 31 additions & 1 deletion cmd/logcli/tail.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package main

import (
"fmt"
"log"
"strings"

"github.com/fatih/color"

"github.com/grafana/loki/pkg/logproto"
)
Expand All @@ -14,6 +18,14 @@ func tailQuery() {

stream := new(logproto.Stream)

if len(*ignoreLabelsKey) > 0 {
fmt.Println("Ingoring labels key:", color.RedString(strings.Join(*ignoreLabelsKey, ",")))
}

if len(*showLabelsKey) > 0 {
fmt.Println("Print only labels key:", color.RedString(strings.Join(*showLabelsKey, ",")))
}

for {
err := conn.ReadJSON(stream)
if err != nil {
Expand All @@ -23,7 +35,25 @@ func tailQuery() {

labels := ""
if !*noLabels {
labels = stream.Labels

if len(*ignoreLabelsKey) > 0 || len(*showLabelsKey) > 0 {

ls := mustParseLabels(stream.GetLabels())

if len(*showLabelsKey) > 0 {
ls = ls.MatchLabels(true, *showLabelsKey...)
}

if len(*ignoreLabelsKey) > 0 {
ls = ls.MatchLabels(false, *ignoreLabelsKey...)
}

labels = ls.String()

} else {

labels = stream.Labels
}
}
for _, entry := range stream.Entries {
printLogEntry(entry.Timestamp, labels, entry.Line)
Expand Down
20 changes: 11 additions & 9 deletions docs/logcli.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,17 @@ usage: logcli query [<flags>] <query> [<regex>]
Run a LogQL query.

Flags:
--help Show context-sensitive help (also try --help-long and --help-man).
--addr="" Server address, need to specify.
--username="" Username for HTTP basic auth.
--password="" Password for HTTP basic auth.
--limit=30 Limit on number of entries to print.
--since=1h Lookback window.
--forward Scan forwards through logs.
-t, --tail Tail the logs
--no-labels Do not print labels
--help Show context-sensitive help (also try --help-long and --help-man).
--addr="" Server address, need to specify.
--username="" Username for HTTP basic auth.
--password="" Password for HTTP basic auth.
--limit=30 Limit on number of entries to print.
--since=1h Lookback window.
--forward Scan forwards through logs.
-t, --tail Tail the logs
--no-labels Do not print any labels
--no-label=NO-LABEL ... Do not print labels given the provided key
--label=LABEL ... Do print labels given the provided key

Args:
<query> eg '{foo="bar",baz="blip"}'
Expand Down