diff --git a/CHANGELOG.md b/CHANGELOG.md index 34646e11c83a..05873ca2c090 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * [1572](https://github.com/grafana/loki/pull/1572) **owen-d**: Introduces the `querier.query-ingesters-within` flag and associated yaml config. When enabled, queries for a time range that do not overlap this lookback interval will not be sent to the ingesters. * [1558](https://github.com/grafana/loki/pull/1558) **owen-d**: Introduces `ingester.max-chunk-age` which specifies the maximum chunk age before it's cut. * [1565](https://github.com/grafana/loki/pull/1565) **owen-d**: The query frontend's `split_queries_by_interval` can now be specified as an override +* [1861](https://github.com/grafana/loki/pull/1565) **yeya24**: Support series command in logcli ## 1.3.0 (2020-01-16) diff --git a/cmd/logcli/main.go b/cmd/logcli/main.go index 869449a36318..d8003f07b846 100644 --- a/cmd/logcli/main.go +++ b/cmd/logcli/main.go @@ -8,14 +8,14 @@ import ( "github.com/prometheus/common/config" "github.com/prometheus/common/version" + "gopkg.in/alecthomas/kingpin.v2" _ "github.com/grafana/loki/pkg/build" "github.com/grafana/loki/pkg/logcli/client" "github.com/grafana/loki/pkg/logcli/labelquery" "github.com/grafana/loki/pkg/logcli/output" "github.com/grafana/loki/pkg/logcli/query" - - "gopkg.in/alecthomas/kingpin.v2" + "github.com/grafana/loki/pkg/logcli/seriesquery" ) var ( @@ -72,6 +72,9 @@ https://github.com/grafana/loki/blob/master/docs/logql.md`) labelsCmd = app.Command("labels", "Find values for a given label.") labelName = labelsCmd.Arg("label", "The name of the label.").HintAction(hintActionLabelNames).String() + + seriesCmd = app.Command("series", "Run series query.") + seriesQuery = newSeriesQuery(seriesCmd) ) func main() { @@ -122,6 +125,8 @@ func main() { q := newLabelQuery(*labelName, *quiet) q.DoLabels(queryClient) + case seriesCmd.FullCommand(): + seriesQuery.DoSeries(queryClient) } } @@ -165,49 +170,76 @@ func newLabelQuery(labelName string, quiet bool) *labelquery.LabelQuery { } } +func newSeriesQuery(cmd *kingpin.CmdClause) *seriesquery.SeriesQuery { + // calculate series range from cli params + var from, to string + var since time.Duration + + q := &seriesquery.SeriesQuery{} + + // executed after all command flags are parsed + cmd.Action(func(c *kingpin.ParseContext) error { + + defaultEnd := time.Now() + defaultStart := defaultEnd.Add(-since) + + q.Start = mustParse(from, defaultStart) + q.End = mustParse(to, defaultEnd) + q.Quiet = *quiet + return nil + }) + + cmd.Flag("since", "Lookback window.").Default("1h").DurationVar(&since) + cmd.Flag("from", "Start looking for logs at this absolute time (inclusive)").StringVar(&from) + cmd.Flag("to", "Stop looking for logs at this absolute time (exclusive)").StringVar(&to) + cmd.Flag("match", "eg '{foo=\"bar\",baz=~\".*blip\"}'").Required().StringsVar(&q.Matchers) + + return q +} + func newQuery(instant bool, cmd *kingpin.CmdClause) *query.Query { - // calculcate query range from cli params + // calculate query range from cli params var now, from, to string var since time.Duration - query := &query.Query{} + q := &query.Query{} // executed after all command flags are parsed cmd.Action(func(c *kingpin.ParseContext) error { if instant { - query.SetInstant(mustParse(now, time.Now())) + q.SetInstant(mustParse(now, time.Now())) } else { defaultEnd := time.Now() defaultStart := defaultEnd.Add(-since) - query.Start = mustParse(from, defaultStart) - query.End = mustParse(to, defaultEnd) + q.Start = mustParse(from, defaultStart) + q.End = mustParse(to, defaultEnd) } - query.Quiet = *quiet + q.Quiet = *quiet return nil }) - cmd.Flag("limit", "Limit on number of entries to print.").Default("30").IntVar(&query.Limit) + cmd.Flag("limit", "Limit on number of entries to print.").Default("30").IntVar(&q.Limit) if instant { - cmd.Arg("query", "eg 'rate({foo=\"bar\"} |~ \".*error.*\" [5m])'").Required().StringVar(&query.QueryString) + cmd.Arg("query", "eg 'rate({foo=\"bar\"} |~ \".*error.*\" [5m])'").Required().StringVar(&q.QueryString) cmd.Flag("now", "Time at which to execute the instant query.").StringVar(&now) } else { - cmd.Arg("query", "eg '{foo=\"bar\",baz=~\".*blip\"} |~ \".*error.*\"'").Required().StringVar(&query.QueryString) + cmd.Arg("query", "eg '{foo=\"bar\",baz=~\".*blip\"} |~ \".*error.*\"'").Required().StringVar(&q.QueryString) cmd.Flag("since", "Lookback window.").Default("1h").DurationVar(&since) cmd.Flag("from", "Start looking for logs at this absolute time (inclusive)").StringVar(&from) cmd.Flag("to", "Stop looking for logs at this absolute time (exclusive)").StringVar(&to) - cmd.Flag("step", "Query resolution step width").DurationVar(&query.Step) + cmd.Flag("step", "Query resolution step width").DurationVar(&q.Step) } - cmd.Flag("forward", "Scan forwards through logs.").Default("false").BoolVar(&query.Forward) - cmd.Flag("no-labels", "Do not print any labels").Default("false").BoolVar(&query.NoLabels) - cmd.Flag("exclude-label", "Exclude labels given the provided key during output.").StringsVar(&query.IgnoreLabelsKey) - cmd.Flag("include-label", "Include labels given the provided key during output.").StringsVar(&query.ShowLabelsKey) - cmd.Flag("labels-length", "Set a fixed padding to labels").Default("0").IntVar(&query.FixedLabelsLen) - cmd.Flag("store-config", "Execute the current query using a configured storage from a given Loki configuration file.").Default("").StringVar(&query.LocalConfig) + cmd.Flag("forward", "Scan forwards through logs.").Default("false").BoolVar(&q.Forward) + cmd.Flag("no-labels", "Do not print any labels").Default("false").BoolVar(&q.NoLabels) + cmd.Flag("exclude-label", "Exclude labels given the provided key during output.").StringsVar(&q.IgnoreLabelsKey) + 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) - return query + return q } func mustParse(t string, defaultTime time.Time) time.Time { diff --git a/pkg/ingester/flush.go b/pkg/ingester/flush.go index d53204fc3ddc..4dfdd9972c92 100644 --- a/pkg/ingester/flush.go +++ b/pkg/ingester/flush.go @@ -331,7 +331,7 @@ func (i *Ingester) flushChunks(ctx context.Context, fp model.Fingerprint, labelP return err } - // Record statistsics only when actual put request did not return error. + // Record statistics only when actual put request did not return error. sizePerTenant := chunkSizePerTenant.WithLabelValues(userID) countPerTenant := chunksPerTenant.WithLabelValues(userID) for i, wc := range wireChunks { diff --git a/pkg/logcli/client/client.go b/pkg/logcli/client/client.go index 8284214fa0b2..60ccc06b3512 100644 --- a/pkg/logcli/client/client.go +++ b/pkg/logcli/client/client.go @@ -24,6 +24,7 @@ const ( queryRangePath = "/loki/api/v1/query_range" labelsPath = "/loki/api/v1/labels" labelValuesPath = "/loki/api/v1/label/%s/values" + seriesPath = "/loki/api/v1/series" tailPath = "/loki/api/v1/tail?query=%s&delay_for=%d&limit=%d&start=%d" ) @@ -89,6 +90,19 @@ func (c *Client) ListLabelValues(name string, quiet bool) (*loghttp.LabelRespons return &labelResponse, nil } +func (c *Client) Series(matchers []string, from, through time.Time, quiet bool) (*loghttp.SeriesResponse, error) { + params := util.NewQueryStringBuilder() + params.SetInt("start", from.UnixNano()) + params.SetInt("end", through.UnixNano()) + params.SetStringArray("match", matchers) + + var seriesResponse loghttp.SeriesResponse + if err := c.doRequest(params.EncodeWithPath(seriesPath), quiet, &seriesResponse); err != nil { + return nil, err + } + return &seriesResponse, nil +} + func (c *Client) doQuery(path string, quiet bool) (*loghttp.QueryResponse, error) { var err error var r loghttp.QueryResponse diff --git a/pkg/logcli/labelquery/labels.go b/pkg/logcli/labelquery/labels.go index 0cd2dff34cce..08d2687ffa53 100644 --- a/pkg/logcli/labelquery/labels.go +++ b/pkg/logcli/labelquery/labels.go @@ -8,7 +8,7 @@ import ( "github.com/grafana/loki/pkg/loghttp" ) -// LabelQuery contains all necessary fields to execute label queries and print out the resutls +// LabelQuery contains all necessary fields to execute label queries and print out the results type LabelQuery struct { LabelName string Quiet bool diff --git a/pkg/logcli/seriesquery/series.go b/pkg/logcli/seriesquery/series.go new file mode 100644 index 000000000000..7bbdc790965e --- /dev/null +++ b/pkg/logcli/seriesquery/series.go @@ -0,0 +1,36 @@ +package seriesquery + +import ( + "fmt" + "log" + "time" + + "github.com/grafana/loki/pkg/logcli/client" + "github.com/grafana/loki/pkg/loghttp" +) + +// SeriesQuery contains all necessary fields to execute label queries and print out the results +type SeriesQuery struct { + Matchers []string + Start time.Time + End time.Time + Quiet bool +} + +// DoSeries prints out series results +func (q *SeriesQuery) DoSeries(c *client.Client) { + values := q.GetSeries(c) + + for _, value := range values { + fmt.Println(value) + } +} + +// GetSeries returns an array of label sets +func (q *SeriesQuery) GetSeries(c *client.Client) []loghttp.LabelSet { + seriesResponse, err := c.Series(q.Matchers, q.Start, q.End, q.Quiet) + if err != nil { + log.Fatalf("Error doing request: %+v", err) + } + return seriesResponse.Data +} diff --git a/pkg/loghttp/params.go b/pkg/loghttp/params.go index 7649a2780472..eef0499f23af 100644 --- a/pkg/loghttp/params.go +++ b/pkg/loghttp/params.go @@ -98,7 +98,7 @@ func Match(xs []string) ([][]*labels.Matcher, error) { } // defaultQueryRangeStep returns the default step used in the query range API, -// which is dinamically calculated based on the time range +// which is dynamically calculated based on the time range func defaultQueryRangeStep(start time.Time, end time.Time) int { return int(math.Max(math.Floor(end.Sub(start).Seconds()/250), 1)) } diff --git a/pkg/util/query_string_builder.go b/pkg/util/query_string_builder.go index 74ad0c7bdc1e..062dbffb5fd9 100644 --- a/pkg/util/query_string_builder.go +++ b/pkg/util/query_string_builder.go @@ -19,6 +19,12 @@ func (b *QueryStringBuilder) SetString(name, value string) { b.values.Set(name, value) } +func (b *QueryStringBuilder) SetStringArray(name string, values []string) { + for _, v := range values { + b.values.Add(name, v) + } +} + func (b *QueryStringBuilder) SetInt(name string, value int64) { b.SetString(name, strconv.FormatInt(value, 10)) }