github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/logcli/query/query.go (about)

     1  package query
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"flag"
     7  	"fmt"
     8  	"log"
     9  	"os"
    10  	"sort"
    11  	"strings"
    12  	"text/tabwriter"
    13  	"time"
    14  
    15  	"github.com/fatih/color"
    16  	json "github.com/json-iterator/go"
    17  	"github.com/prometheus/client_golang/prometheus"
    18  	"github.com/weaveworks/common/user"
    19  	"gopkg.in/yaml.v2"
    20  
    21  	"github.com/grafana/loki/pkg/logcli/client"
    22  	"github.com/grafana/loki/pkg/logcli/output"
    23  	"github.com/grafana/loki/pkg/loghttp"
    24  	"github.com/grafana/loki/pkg/logproto"
    25  	"github.com/grafana/loki/pkg/logql"
    26  	"github.com/grafana/loki/pkg/logqlmodel"
    27  	"github.com/grafana/loki/pkg/logqlmodel/stats"
    28  	"github.com/grafana/loki/pkg/loki"
    29  	"github.com/grafana/loki/pkg/storage"
    30  	chunk "github.com/grafana/loki/pkg/storage/chunk/client"
    31  	"github.com/grafana/loki/pkg/storage/config"
    32  	"github.com/grafana/loki/pkg/storage/stores/indexshipper"
    33  	"github.com/grafana/loki/pkg/util/cfg"
    34  	util_log "github.com/grafana/loki/pkg/util/log"
    35  	"github.com/grafana/loki/pkg/util/marshal"
    36  	"github.com/grafana/loki/pkg/validation"
    37  )
    38  
    39  const SchemaConfigFilename = "schemaconfig.yaml"
    40  
    41  type streamEntryPair struct {
    42  	entry  loghttp.Entry
    43  	labels loghttp.LabelSet
    44  }
    45  
    46  // Query contains all necessary fields to execute instant and range queries and print the results.
    47  type Query struct {
    48  	QueryString            string
    49  	Start                  time.Time
    50  	End                    time.Time
    51  	Limit                  int
    52  	BatchSize              int
    53  	Forward                bool
    54  	Step                   time.Duration
    55  	Interval               time.Duration
    56  	Quiet                  bool
    57  	NoLabels               bool
    58  	IgnoreLabelsKey        []string
    59  	ShowLabelsKey          []string
    60  	FixedLabelsLen         int
    61  	ColoredOutput          bool
    62  	LocalConfig            string
    63  	FetchSchemaFromStorage bool
    64  }
    65  
    66  // DoQuery executes the query and prints out the results
    67  func (q *Query) DoQuery(c client.Client, out output.LogOutput, statistics bool) {
    68  	if q.LocalConfig != "" {
    69  		orgID := c.GetOrgID()
    70  		if orgID == "" {
    71  			orgID = "fake"
    72  		}
    73  		if err := q.DoLocalQuery(out, statistics, orgID, q.FetchSchemaFromStorage); err != nil {
    74  			log.Fatalf("Query failed: %+v", err)
    75  		}
    76  		return
    77  	}
    78  
    79  	d := q.resultsDirection()
    80  
    81  	var resp *loghttp.QueryResponse
    82  	var err error
    83  
    84  	if q.isInstant() {
    85  		resp, err = c.Query(q.QueryString, q.Limit, q.Start, d, q.Quiet)
    86  		if err != nil {
    87  			log.Fatalf("Query failed: %+v", err)
    88  		}
    89  		if statistics {
    90  			q.printStats(resp.Data.Statistics)
    91  		}
    92  		_, _ = q.printResult(resp.Data.Result, out, nil)
    93  	} else {
    94  		if q.Limit < q.BatchSize {
    95  			q.BatchSize = q.Limit
    96  		}
    97  		resultLength := 0
    98  		total := 0
    99  		start := q.Start
   100  		end := q.End
   101  		var lastEntry []*loghttp.Entry
   102  		for total < q.Limit {
   103  			bs := q.BatchSize
   104  			// We want to truncate the batch size if the remaining number
   105  			// of items needed to reach the limit is less than the batch size
   106  			if q.Limit-total < q.BatchSize {
   107  				// Truncated batchsize is q.Limit - total, however we add to this
   108  				// the length of the overlap from the last query to make sure we get the
   109  				// correct amount of new logs knowing there will be some overlapping logs returned.
   110  				bs = q.Limit - total + len(lastEntry)
   111  			}
   112  			resp, err = c.QueryRange(q.QueryString, bs, start, end, d, q.Step, q.Interval, q.Quiet)
   113  			if err != nil {
   114  				log.Fatalf("Query failed: %+v", err)
   115  			}
   116  
   117  			if statistics {
   118  				q.printStats(resp.Data.Statistics)
   119  			}
   120  
   121  			resultLength, lastEntry = q.printResult(resp.Data.Result, out, lastEntry)
   122  			// Was not a log stream query, or no results, no more batching
   123  			if resultLength <= 0 {
   124  				break
   125  			}
   126  			// Also no result, wouldn't expect to hit this.
   127  			if len(lastEntry) == 0 {
   128  				break
   129  			}
   130  			// Can only happen if all the results return in one request
   131  			if resultLength == q.Limit {
   132  				break
   133  			}
   134  			if len(lastEntry) >= q.BatchSize {
   135  				log.Fatalf("Invalid batch size %v, the next query will have %v overlapping entries "+
   136  					"(there will always be 1 overlapping entry but Loki allows multiple entries to have "+
   137  					"the same timestamp, so when a batch ends in this scenario the next query will include "+
   138  					"all the overlapping entries again).  Please increase your batch size to at least %v to account "+
   139  					"for overlapping entryes\n", q.BatchSize, len(lastEntry), len(lastEntry)+1)
   140  			}
   141  
   142  			// Batching works by taking the timestamp of the last query and using it in the next query,
   143  			// because Loki supports multiple entries with the same timestamp it's possible for a batch to have
   144  			// fallen in the middle of a list of entries for the same time, so to make sure we get all entries
   145  			// we start the query on the same time as the last entry from the last batch, and then we keep this last
   146  			// entry and remove the duplicate when printing the results.
   147  			// Because of this duplicate entry, we have to subtract it here from the total for each batch
   148  			// to get the desired limit.
   149  			total += resultLength
   150  			// Based on the query direction we either set the start or end for the next query.
   151  			// If there are multiple entries in `lastEntry` they have to have the same timestamp so we can pick just the first
   152  			if q.Forward {
   153  				start = lastEntry[0].Timestamp
   154  			} else {
   155  				// The end timestamp is exclusive on a backward query, so to make sure we get back an overlapping result
   156  				// fudge the timestamp forward in time to make sure to get the last entry from this batch in the next query
   157  				end = lastEntry[0].Timestamp.Add(1 * time.Nanosecond)
   158  			}
   159  
   160  		}
   161  	}
   162  }
   163  
   164  func (q *Query) printResult(value loghttp.ResultValue, out output.LogOutput, lastEntry []*loghttp.Entry) (int, []*loghttp.Entry) {
   165  	length := -1
   166  	var entry []*loghttp.Entry
   167  	switch value.Type() {
   168  	case logqlmodel.ValueTypeStreams:
   169  		length, entry = q.printStream(value.(loghttp.Streams), out, lastEntry)
   170  	case loghttp.ResultTypeScalar:
   171  		q.printScalar(value.(loghttp.Scalar))
   172  	case loghttp.ResultTypeMatrix:
   173  		q.printMatrix(value.(loghttp.Matrix))
   174  	case loghttp.ResultTypeVector:
   175  		q.printVector(value.(loghttp.Vector))
   176  	default:
   177  		log.Fatalf("Unable to print unsupported type: %v", value.Type())
   178  	}
   179  	return length, entry
   180  }
   181  
   182  // DoLocalQuery executes the query against the local store using a Loki configuration file.
   183  func (q *Query) DoLocalQuery(out output.LogOutput, statistics bool, orgID string, useRemoteSchema bool) error {
   184  	var conf loki.Config
   185  	conf.RegisterFlags(flag.CommandLine)
   186  	if q.LocalConfig == "" {
   187  		return errors.New("no supplied config file")
   188  	}
   189  	if err := cfg.YAML(q.LocalConfig, false)(&conf); err != nil {
   190  		return err
   191  	}
   192  
   193  	if err := conf.Validate(); err != nil {
   194  		return err
   195  	}
   196  
   197  	limits, err := validation.NewOverrides(conf.LimitsConfig, nil)
   198  	if err != nil {
   199  		return err
   200  	}
   201  	cm := storage.NewClientMetrics()
   202  	conf.StorageConfig.BoltDBShipperConfig.Mode = indexshipper.ModeReadOnly
   203  	conf.StorageConfig.BoltDBShipperConfig.IndexGatewayClientConfig.Disabled = true
   204  
   205  	schema := conf.SchemaConfig
   206  	if useRemoteSchema {
   207  		cm := storage.NewClientMetrics()
   208  		client, err := GetObjectClient(conf, cm)
   209  		if err != nil {
   210  			return err
   211  		}
   212  
   213  		loadedSchema, err := LoadSchemaUsingObjectClient(client, SchemaConfigFilename)
   214  		if err != nil {
   215  			return err
   216  		}
   217  
   218  		schema = *loadedSchema
   219  	}
   220  
   221  	querier, err := storage.NewStore(conf.StorageConfig, conf.ChunkStoreConfig, schema, limits, cm, prometheus.DefaultRegisterer, util_log.Logger)
   222  	if err != nil {
   223  		return err
   224  	}
   225  
   226  	eng := logql.NewEngine(conf.Querier.Engine, querier, limits, util_log.Logger)
   227  	var query logql.Query
   228  
   229  	if q.isInstant() {
   230  		query = eng.Query(logql.NewLiteralParams(
   231  			q.QueryString,
   232  			q.Start,
   233  			q.Start,
   234  			0,
   235  			0,
   236  			q.resultsDirection(),
   237  			uint32(q.Limit),
   238  			nil,
   239  		))
   240  	} else {
   241  		query = eng.Query(logql.NewLiteralParams(
   242  			q.QueryString,
   243  			q.Start,
   244  			q.End,
   245  			q.Step,
   246  			q.Interval,
   247  			q.resultsDirection(),
   248  			uint32(q.Limit),
   249  			nil,
   250  		))
   251  	}
   252  
   253  	// execute the query
   254  	ctx := user.InjectOrgID(context.Background(), orgID)
   255  	result, err := query.Exec(ctx)
   256  	if err != nil {
   257  		return err
   258  	}
   259  
   260  	if statistics {
   261  		q.printStats(result.Statistics)
   262  	}
   263  
   264  	value, err := marshal.NewResultValue(result.Data)
   265  	if err != nil {
   266  		return err
   267  	}
   268  
   269  	q.printResult(value, out, nil)
   270  	return nil
   271  }
   272  
   273  func GetObjectClient(conf loki.Config, cm storage.ClientMetrics) (chunk.ObjectClient, error) {
   274  	oc, err := storage.NewObjectClient(
   275  		conf.StorageConfig.BoltDBShipperConfig.SharedStoreType,
   276  		conf.StorageConfig,
   277  		cm,
   278  	)
   279  	if err != nil {
   280  		return nil, err
   281  	}
   282  	return oc, nil
   283  }
   284  
   285  type schemaConfigSection struct {
   286  	config.SchemaConfig `yaml:"schema_config"`
   287  }
   288  
   289  func LoadSchemaUsingObjectClient(oc chunk.ObjectClient, name string) (*config.SchemaConfig, error) {
   290  	ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))
   291  	defer cancel()
   292  	rdr, _, err := oc.GetObject(ctx, name)
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  	defer rdr.Close()
   297  
   298  	decoder := yaml.NewDecoder(rdr)
   299  	decoder.SetStrict(true)
   300  	section := schemaConfigSection{}
   301  	err = decoder.Decode(&section)
   302  	if err != nil {
   303  		return nil, err
   304  	}
   305  
   306  	return &section.SchemaConfig, nil
   307  }
   308  
   309  // SetInstant makes the Query an instant type
   310  func (q *Query) SetInstant(time time.Time) {
   311  	q.Start = time
   312  	q.End = time
   313  }
   314  
   315  func (q *Query) isInstant() bool {
   316  	return q.Start == q.End && q.Step == 0
   317  }
   318  
   319  func (q *Query) printStream(streams loghttp.Streams, out output.LogOutput, lastEntry []*loghttp.Entry) (int, []*loghttp.Entry) {
   320  	common := commonLabels(streams)
   321  
   322  	// Remove the labels we want to show from common
   323  	if len(q.ShowLabelsKey) > 0 {
   324  		common = matchLabels(false, common, q.ShowLabelsKey)
   325  	}
   326  
   327  	if len(common) > 0 && !q.Quiet {
   328  		log.Println("Common labels:", color.RedString(common.String()))
   329  	}
   330  
   331  	if len(q.IgnoreLabelsKey) > 0 && !q.Quiet {
   332  		log.Println("Ignoring labels key:", color.RedString(strings.Join(q.IgnoreLabelsKey, ",")))
   333  	}
   334  
   335  	if len(q.ShowLabelsKey) > 0 && !q.Quiet {
   336  		log.Println("Print only labels key:", color.RedString(strings.Join(q.ShowLabelsKey, ",")))
   337  	}
   338  
   339  	// Remove ignored and common labels from the cached labels and
   340  	// calculate the max labels length
   341  	maxLabelsLen := q.FixedLabelsLen
   342  	for i, s := range streams {
   343  		// Remove common labels
   344  		ls := subtract(s.Labels, common)
   345  
   346  		if len(q.ShowLabelsKey) > 0 {
   347  			ls = matchLabels(true, ls, q.ShowLabelsKey)
   348  		}
   349  
   350  		// Remove ignored labels
   351  		if len(q.IgnoreLabelsKey) > 0 {
   352  			ls = matchLabels(false, ls, q.IgnoreLabelsKey)
   353  		}
   354  
   355  		// Overwrite existing Labels
   356  		streams[i].Labels = ls
   357  
   358  		// Update max labels length
   359  		len := len(ls.String())
   360  		if maxLabelsLen < len {
   361  			maxLabelsLen = len
   362  		}
   363  	}
   364  
   365  	// sort and display entries
   366  	allEntries := make([]streamEntryPair, 0)
   367  
   368  	for _, s := range streams {
   369  		for _, e := range s.Entries {
   370  			allEntries = append(allEntries, streamEntryPair{
   371  				entry:  e,
   372  				labels: s.Labels,
   373  			})
   374  		}
   375  	}
   376  
   377  	if len(allEntries) == 0 {
   378  		return 0, nil
   379  	}
   380  
   381  	if q.Forward {
   382  		sort.Slice(allEntries, func(i, j int) bool { return allEntries[i].entry.Timestamp.Before(allEntries[j].entry.Timestamp) })
   383  	} else {
   384  		sort.Slice(allEntries, func(i, j int) bool { return allEntries[i].entry.Timestamp.After(allEntries[j].entry.Timestamp) })
   385  	}
   386  
   387  	printed := 0
   388  	for _, e := range allEntries {
   389  		// Skip the last entry if it overlaps, this happens because batching includes the last entry from the last batch
   390  		if len(lastEntry) > 0 && e.entry.Timestamp == lastEntry[0].Timestamp {
   391  			skip := false
   392  			// Because many logs can share a timestamp in the unlucky event a batch ends with a timestamp
   393  			// shared by multiple entries we have to check all that were stored to see if we've already
   394  			// printed them.
   395  			for _, le := range lastEntry {
   396  				if e.entry.Line == le.Line {
   397  					skip = true
   398  				}
   399  			}
   400  			if skip {
   401  				continue
   402  			}
   403  		}
   404  		out.FormatAndPrintln(e.entry.Timestamp, e.labels, maxLabelsLen, e.entry.Line)
   405  		printed++
   406  	}
   407  
   408  	// Loki allows multiple entries at the same timestamp, this is a bit of a mess if a batch ends
   409  	// with an entry that shared multiple timestamps, so we need to keep a list of all these entries
   410  	// because the next query is going to contain them too and we want to not duplicate anything already
   411  	// printed.
   412  	lel := []*loghttp.Entry{}
   413  	// Start with the timestamp of the last entry
   414  	le := allEntries[len(allEntries)-1].entry
   415  	for i, e := range allEntries {
   416  		// Save any entry which has this timestamp (most of the time this will only be the single last entry)
   417  		if e.entry.Timestamp.Equal(le.Timestamp) {
   418  			lel = append(lel, &allEntries[i].entry)
   419  		}
   420  	}
   421  
   422  	return printed, lel
   423  }
   424  
   425  func (q *Query) printMatrix(matrix loghttp.Matrix) {
   426  	// yes we are effectively unmarshalling and then immediately marshalling this object back to json.  we are doing this b/c
   427  	// it gives us more flexibility with regard to output types in the future.  initially we are supporting just formatted json but eventually
   428  	// we might add output options such as render to an image file on disk
   429  	bytes, err := json.MarshalIndent(matrix, "", "  ")
   430  	if err != nil {
   431  		log.Fatalf("Error marshalling matrix: %v", err)
   432  	}
   433  
   434  	fmt.Print(string(bytes))
   435  }
   436  
   437  func (q *Query) printVector(vector loghttp.Vector) {
   438  	bytes, err := json.MarshalIndent(vector, "", "  ")
   439  	if err != nil {
   440  		log.Fatalf("Error marshalling vector: %v", err)
   441  	}
   442  
   443  	fmt.Print(string(bytes))
   444  }
   445  
   446  func (q *Query) printScalar(scalar loghttp.Scalar) {
   447  	bytes, err := json.MarshalIndent(scalar, "", "  ")
   448  	if err != nil {
   449  		log.Fatalf("Error marshalling scalar: %v", err)
   450  	}
   451  
   452  	fmt.Print(string(bytes))
   453  }
   454  
   455  type kvLogger struct {
   456  	*tabwriter.Writer
   457  }
   458  
   459  func (k kvLogger) Log(keyvals ...interface{}) error {
   460  	for i := 0; i < len(keyvals); i += 2 {
   461  		fmt.Fprintln(k.Writer, color.BlueString("%s", keyvals[i]), "\t", fmt.Sprintf("%v", keyvals[i+1]))
   462  	}
   463  	k.Flush()
   464  	return nil
   465  }
   466  
   467  func (q *Query) printStats(stats stats.Result) {
   468  	writer := tabwriter.NewWriter(os.Stderr, 0, 8, 0, '\t', 0)
   469  	stats.Log(kvLogger{Writer: writer})
   470  }
   471  
   472  func (q *Query) resultsDirection() logproto.Direction {
   473  	if q.Forward {
   474  		return logproto.FORWARD
   475  	}
   476  	return logproto.BACKWARD
   477  }