From 8f7591dca0f3f5cfdff106f3c9d050dec5f9d693 Mon Sep 17 00:00:00 2001 From: Aditya C S Date: Thu, 6 Aug 2020 12:58:43 +0530 Subject: [PATCH 1/2] colored labels output for logcli --- cmd/logcli/main.go | 12 ++++--- docs/sources/getting-started/logcli.md | 1 + pkg/logcli/output/default.go | 5 +++ pkg/logcli/output/default_test.go | 43 ++++++++++++++++++++++++++ pkg/logcli/output/output.go | 31 +++++++++++++++++-- pkg/logcli/output/output_test.go | 2 +- pkg/logcli/query/query.go | 4 +-- 7 files changed, 88 insertions(+), 10 deletions(-) diff --git a/cmd/logcli/main.go b/cmd/logcli/main.go index b5bf5381aa8c6..165e30e2da921 100644 --- a/cmd/logcli/main.go +++ b/cmd/logcli/main.go @@ -25,7 +25,6 @@ var ( statistics = app.Flag("stats", "Show query statistics").Default("false").Bool() outputMode = app.Flag("output", "Specify output mode [default, raw, jsonl]. raw suppresses log labels and timestamp.").Default("default").Short('o').Enum("default", "raw", "jsonl") timezone = app.Flag("timezone", "Specify the timezone to use when formatting output timestamps [Local, UTC]").Default("Local").Short('z').Enum("Local", "UTC") - cpuProfile = app.Flag("cpuprofile", "Specify the location for writing a CPU profile.").Default("").String() memProfile = app.Flag("memprofile", "Specify the location for writing a memory profile.").Default("").String() @@ -119,8 +118,9 @@ func main() { } outputOptions := &output.LogOutputOptions{ - Timezone: location, - NoLabels: rangeQuery.NoLabels, + Timezone: location, + NoLabels: rangeQuery.NoLabels, + ColoredOutput: rangeQuery.ColoredOutput, } out, err := output.NewLogOutput(*outputMode, outputOptions) @@ -140,8 +140,9 @@ func main() { } outputOptions := &output.LogOutputOptions{ - Timezone: location, - NoLabels: instantQuery.NoLabels, + Timezone: location, + NoLabels: instantQuery.NoLabels, + ColoredOutput: instantQuery.ColoredOutput, } out, err := output.NewLogOutput(*outputMode, outputOptions) @@ -280,6 +281,7 @@ func newQuery(instant bool, cmd *kingpin.CmdClause) *query.Query { cmd.Flag("include-label", "Include labels given the provided key during output.").StringsVar(&q.ShowLabelsKey) cmd.Flag("labels-length", "Set a fixed padding to labels").Default("0").IntVar(&q.FixedLabelsLen) cmd.Flag("store-config", "Execute the current query using a configured storage from a given Loki configuration file.").Default("").StringVar(&q.LocalConfig) + cmd.Flag("colored-output", "Show ouput with colored labels").Default("false").BoolVar(&q.ColoredOutput) return q } diff --git a/docs/sources/getting-started/logcli.md b/docs/sources/getting-started/logcli.md index 628b210b8d25a..75620bf52f455 100644 --- a/docs/sources/getting-started/logcli.md +++ b/docs/sources/getting-started/logcli.md @@ -198,6 +198,7 @@ Flags: --store-config="" Execute the current query using a configured storage from a given Loki configuration file. -t, --tail Tail the logs --delay-for=0 Delay in tailing by number of seconds to accumulate logs for re-ordering + --colored-output Show ouput with colored labels Args: eg '{foo="bar",baz=~".*blip"} |~ ".*error.*"' diff --git a/pkg/logcli/output/default.go b/pkg/logcli/output/default.go index 0a3e1bcb96eae..c23554a9f707b 100644 --- a/pkg/logcli/output/default.go +++ b/pkg/logcli/output/default.go @@ -24,6 +24,11 @@ func (o *DefaultOutput) Format(ts time.Time, lbls loghttp.LabelSet, maxLabelsLen return fmt.Sprintf("%s %s", color.BlueString(timestamp), line) } + if o.options.ColoredOutput { + labelsColor := getColor(lbls.String()).SprintFunc() + return fmt.Sprintf("%s %s %s", color.BlueString(timestamp), labelsColor(padLabel(lbls, maxLabelsLen)), line) + } + return fmt.Sprintf("%s %s %s", color.BlueString(timestamp), color.RedString(padLabel(lbls, maxLabelsLen)), line) } diff --git a/pkg/logcli/output/default_test.go b/pkg/logcli/output/default_test.go index 8cbe5b038dfe2..5f6dbb1b99617 100644 --- a/pkg/logcli/output/default_test.go +++ b/pkg/logcli/output/default_test.go @@ -132,6 +132,49 @@ func TestDefaultOutput_FormatLabelsPadding(t *testing.T) { } } +func TestColorForLabels(t *testing.T) { + tests := map[string]struct { + labels loghttp.LabelSet + otherLabels loghttp.LabelSet + expected bool + }{ + + "different labels": { + loghttp.LabelSet(map[string]string{ + "type": "test", + "app": "loki", + }), + loghttp.LabelSet(map[string]string{ + "type": "test", + "app": "grafana-loki", + }), + false, + }, + "same labels": { + loghttp.LabelSet(map[string]string{ + "type": "test", + "app": "loki", + }), + loghttp.LabelSet(map[string]string{ + "type": "test", + "app": "loki", + }), + true, + }, + } + + for testName, testData := range tests { + testData := testData + + t.Run(testName, func(t *testing.T) { + t.Parallel() + labelsColor := getColor(testData.labels.String()) + otherLablesColor := getColor(testData.otherLabels.String()) + assert.Equal(t, testData.expected, labelsColor.Equals(otherLablesColor)) + }) + } +} + func findMaxLabelsLength(labelsList []loghttp.LabelSet) int { maxLabelsLen := 0 diff --git a/pkg/logcli/output/output.go b/pkg/logcli/output/output.go index 5963306e9087b..fa181d8d5d1af 100644 --- a/pkg/logcli/output/output.go +++ b/pkg/logcli/output/output.go @@ -2,11 +2,29 @@ package output import ( "fmt" + "hash/fnv" "time" + "github.com/fatih/color" + "github.com/grafana/loki/pkg/loghttp" ) +// Blue color is excluded since we are already printing timestamp +// in blue color +var colorList = []*color.Color{ + color.New(color.FgHiCyan), + color.New(color.FgCyan), + color.New(color.FgHiGreen), + color.New(color.FgGreen), + color.New(color.FgHiMagenta), + color.New(color.FgMagenta), + color.New(color.FgHiYellow), + color.New(color.FgYellow), + color.New(color.FgHiRed), + color.New(color.FgRed), +} + // LogOutput is the interface any output mode must implement type LogOutput interface { Format(ts time.Time, lbls loghttp.LabelSet, maxLabelsLen int, line string) string @@ -14,8 +32,9 @@ type LogOutput interface { // LogOutputOptions defines options supported by LogOutput type LogOutputOptions struct { - Timezone *time.Location - NoLabels bool + Timezone *time.Location + NoLabels bool + ColoredOutput bool } // NewLogOutput creates a log output based on the input mode and options @@ -41,3 +60,11 @@ func NewLogOutput(mode string, options *LogOutputOptions) (LogOutput, error) { return nil, fmt.Errorf("unknown log output mode '%s'", mode) } } + +func getColor(labels string) *color.Color { + hash := fnv.New32() + hash.Write([]byte(labels)) + id := hash.Sum32() % uint32(len(colorList)) + color := colorList[id] + return color +} diff --git a/pkg/logcli/output/output_test.go b/pkg/logcli/output/output_test.go index 4e57211d05578..914b36e0cdde7 100644 --- a/pkg/logcli/output/output_test.go +++ b/pkg/logcli/output/output_test.go @@ -8,7 +8,7 @@ import ( ) func TestNewLogOutput(t *testing.T) { - options := &LogOutputOptions{time.UTC, false} + options := &LogOutputOptions{time.UTC, false, false} out, err := NewLogOutput("default", options) assert.NoError(t, err) diff --git a/pkg/logcli/query/query.go b/pkg/logcli/query/query.go index 71ba0e874c96e..778dbecb3a656 100644 --- a/pkg/logcli/query/query.go +++ b/pkg/logcli/query/query.go @@ -50,8 +50,8 @@ type Query struct { IgnoreLabelsKey []string ShowLabelsKey []string FixedLabelsLen int - - LocalConfig string + ColoredOutput bool + LocalConfig string } // DoQuery executes the query and prints out the results From 9625e626a08192ba23ae62ee62755e0842ea0a2a Mon Sep 17 00:00:00 2001 From: Aditya C S Date: Sat, 8 Aug 2020 20:07:50 +0530 Subject: [PATCH 2/2] add period to all cli description --- docs/sources/getting-started/logcli.md | 48 +++++++++++++------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/sources/getting-started/logcli.md b/docs/sources/getting-started/logcli.md index 75620bf52f455..769898261f604 100644 --- a/docs/sources/getting-started/logcli.md +++ b/docs/sources/getting-started/logcli.md @@ -80,10 +80,10 @@ A command-line for loki. Flags: --help Show context-sensitive help (also try --help-long and --help-man). --version Show application version. - -q, --quiet Suppress query metadata - --stats Show query statistics + -q, --quiet Suppress query metadata. + --stats Show query statistics. -o, --output=default Specify output mode [default, raw, jsonl]. raw suppresses log labels and timestamp. - -z, --timezone=Local Specify the timezone to use when formatting output timestamps [Local, UTC] + -z, --timezone=Local Specify the timezone to use when formatting output timestamps [Local, UTC]. --cpuprofile="" Specify the location for writing a CPU profile. --memprofile="" Specify the location for writing a memory profile. --addr="http://localhost:3100" @@ -164,10 +164,10 @@ instead. Flags: --help Show context-sensitive help (also try --help-long and --help-man). --version Show application version. - -q, --quiet Suppress query metadata - --stats Show query statistics + -q, --quiet Suppress query metadata. + --stats Show query statistics. -o, --output=default Specify output mode [default, raw, jsonl]. raw suppresses log labels and timestamp. - -z, --timezone=Local Specify the timezone to use when formatting output timestamps [Local, UTC] + -z, --timezone=Local Specify the timezone to use when formatting output timestamps [Local, UTC]. --cpuprofile="" Specify the location for writing a CPU profile. --memprofile="" Specify the location for writing a memory profile. --addr="http://localhost:3100" @@ -182,23 +182,23 @@ Flags: bypassing an auth gateway. --limit=30 Limit on number of entries to print. --since=1h Lookback window. - --from=FROM Start looking for logs at this absolute time (inclusive) - --to=TO Stop looking for logs at this absolute time (exclusive) + --from=FROM Start looking for logs at this absolute time (inclusive). + --to=TO Stop looking for logs at this absolute time (exclusive). --step=STEP Query resolution step width, for metric queries. Evaluate the query at the specified step over the time range. --interval=INTERVAL Query interval, for log queries. Return entries at the specified interval, ignoring those between. - **This parameter is experimental, please see Issue 1779** + **This parameter is experimental, please see Issue 1779**. --forward Scan forwards through logs. - --no-labels Do not print any labels + --no-labels Do not print any labels. --exclude-label=EXCLUDE-LABEL ... Exclude labels given the provided key during output. --include-label=INCLUDE-LABEL ... Include labels given the provided key during output. - --labels-length=0 Set a fixed padding to labels + --labels-length=0 Set a fixed padding to labels. --store-config="" Execute the current query using a configured storage from a given Loki configuration file. - -t, --tail Tail the logs - --delay-for=0 Delay in tailing by number of seconds to accumulate logs for re-ordering - --colored-output Show ouput with colored labels + -t, --tail Tail the logs. + --delay-for=0 Delay in tailing by number of seconds to accumulate logs for re-ordering. + --colored-output Show ouput with colored labels. Args: eg '{foo="bar",baz=~".*blip"} |~ ".*error.*"' @@ -211,10 +211,10 @@ Find values for a given label. Flags: --help Show context-sensitive help (also try --help-long and --help-man). --version Show application version. - -q, --quiet Suppress query metadata - --stats Show query statistics + -q, --quiet Suppress query metadata. + --stats Show query statistics. -o, --output=default Specify output mode [default, raw, jsonl]. raw suppresses log labels and timestamp. - -z, --timezone=Local Specify the timezone to use when formatting output timestamps [Local, UTC] + -z, --timezone=Local Specify the timezone to use when formatting output timestamps [Local, UTC]. --cpuprofile="" Specify the location for writing a CPU profile. --memprofile="" Specify the location for writing a memory profile. --addr="http://localhost:3100" @@ -228,8 +228,8 @@ Flags: --org-id="" adds X-Scope-OrgID to API requests for representing tenant ID. Useful for requesting tenant data when bypassing an auth gateway. --since=1h Lookback window. - --from=FROM Start looking for labels at this absolute time (inclusive) - --to=TO Stop looking for labels at this absolute time (exclusive) + --from=FROM Start looking for labels at this absolute time (inclusive). + --to=TO Stop looking for labels at this absolute time (exclusive). Args: [