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(§ion) 302 if err != nil { 303 return nil, err 304 } 305 306 return §ion.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 }