mirror of https://github.com/grafana/loki
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
4.6 KiB
170 lines
4.6 KiB
|
5 years ago
|
package deletion
|
||
|
|
|
||
|
|
import (
|
||
|
3 years ago
|
"time"
|
||
|
|
|
||
|
4 years ago
|
"github.com/go-kit/log/level"
|
||
|
3 years ago
|
"github.com/prometheus/common/model"
|
||
|
|
"github.com/prometheus/prometheus/model/labels"
|
||
|
|
|
||
|
2 years ago
|
"github.com/grafana/loki/pkg/compactor/retention"
|
||
|
4 years ago
|
"github.com/grafana/loki/pkg/logql/syntax"
|
||
|
|
"github.com/grafana/loki/pkg/util/filter"
|
||
|
|
util_log "github.com/grafana/loki/pkg/util/log"
|
||
|
5 years ago
|
)
|
||
|
|
|
||
|
3 years ago
|
type timeInterval struct {
|
||
|
|
start, end time.Time
|
||
|
|
}
|
||
|
|
|
||
|
5 years ago
|
type DeleteRequest struct {
|
||
|
|
RequestID string `json:"request_id"`
|
||
|
|
StartTime model.Time `json:"start_time"`
|
||
|
|
EndTime model.Time `json:"end_time"`
|
||
|
4 years ago
|
Query string `json:"query"`
|
||
|
5 years ago
|
Status DeleteRequestStatus `json:"status"`
|
||
|
|
CreatedAt model.Time `json:"created_at"`
|
||
|
|
|
||
|
3 years ago
|
UserID string `json:"-"`
|
||
|
3 years ago
|
SequenceNum int64 `json:"-"`
|
||
|
3 years ago
|
matchers []*labels.Matcher `json:"-"`
|
||
|
|
logSelectorExpr syntax.LogSelectorExpr `json:"-"`
|
||
|
3 years ago
|
timeInterval *timeInterval `json:"-"`
|
||
|
3 years ago
|
|
||
|
|
Metrics *deleteRequestsManagerMetrics `json:"-"`
|
||
|
|
DeletedLines int32 `json:"-"`
|
||
|
4 years ago
|
}
|
||
|
|
|
||
|
|
func (d *DeleteRequest) SetQuery(logQL string) error {
|
||
|
|
d.Query = logQL
|
||
|
4 years ago
|
logSelectorExpr, err := parseDeletionQuery(logQL)
|
||
|
4 years ago
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
4 years ago
|
d.logSelectorExpr = logSelectorExpr
|
||
|
|
d.matchers = logSelectorExpr.Matchers()
|
||
|
4 years ago
|
return nil
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
3 years ago
|
// FilterFunction returns a filter function that returns true if the given line should be deleted based on the DeleteRequest
|
||
|
2 years ago
|
func (d *DeleteRequest) FilterFunction(lbls labels.Labels) (filter.Func, error) {
|
||
|
3 years ago
|
// init d.timeInterval used to efficiently check log ts is within the bounds of delete request below in filter func
|
||
|
|
// without having to do conversion of timestamps for each log line we check.
|
||
|
|
if d.timeInterval == nil {
|
||
|
|
d.timeInterval = &timeInterval{
|
||
|
|
start: d.StartTime.Time(),
|
||
|
|
end: d.EndTime.Time(),
|
||
|
4 years ago
|
}
|
||
|
|
}
|
||
|
3 years ago
|
|
||
|
2 years ago
|
if !allMatch(d.matchers, lbls) {
|
||
|
|
return func(_ time.Time, _ string, _ ...labels.Label) bool {
|
||
|
3 years ago
|
return false
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// if delete request doesn't have a line filter, just do time based filtering
|
||
|
3 years ago
|
if !d.logSelectorExpr.HasFilter() {
|
||
|
2 years ago
|
return func(ts time.Time, _ string, _ ...labels.Label) bool {
|
||
|
3 years ago
|
if ts.Before(d.timeInterval.start) || ts.After(d.timeInterval.end) {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
return true
|
||
|
|
}, nil
|
||
|
3 years ago
|
}
|
||
|
|
|
||
|
4 years ago
|
p, err := d.logSelectorExpr.Pipeline()
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
2 years ago
|
f := p.ForStream(lbls).ProcessString
|
||
|
2 years ago
|
return func(ts time.Time, s string, structuredMetadata ...labels.Label) bool {
|
||
|
3 years ago
|
if ts.Before(d.timeInterval.start) || ts.After(d.timeInterval.end) {
|
||
|
4 years ago
|
return false
|
||
|
3 years ago
|
}
|
||
|
4 years ago
|
|
||
|
2 years ago
|
result, _, skip := f(0, s, structuredMetadata...)
|
||
|
4 years ago
|
if len(result) != 0 || skip {
|
||
|
3 years ago
|
d.Metrics.deletedLinesTotal.WithLabelValues(d.UserID).Inc()
|
||
|
|
d.DeletedLines++
|
||
|
4 years ago
|
return true
|
||
|
|
}
|
||
|
|
return false
|
||
|
4 years ago
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func allMatch(matchers []*labels.Matcher, labels labels.Labels) bool {
|
||
|
|
for _, m := range matchers {
|
||
|
|
if !m.Matches(labels.Get(m.Name)) {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
|
||
|
|
// IsDeleted checks if the given ChunkEntry will be deleted by this DeleteRequest.
|
||
|
3 years ago
|
// It returns a filter.Func if the chunk is supposed to be deleted partially or the delete request contains line filters.
|
||
|
|
// If the filter.Func is nil, the whole chunk is supposed to be deleted.
|
||
|
|
func (d *DeleteRequest) IsDeleted(entry retention.ChunkEntry) (bool, filter.Func) {
|
||
|
5 years ago
|
if d.UserID != unsafeGetString(entry.UserID) {
|
||
|
|
return false, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
if !intervalsOverlap(model.Interval{
|
||
|
|
Start: entry.From,
|
||
|
|
End: entry.Through,
|
||
|
|
}, model.Interval{
|
||
|
|
Start: d.StartTime,
|
||
|
|
End: d.EndTime,
|
||
|
|
}) {
|
||
|
|
return false, nil
|
||
|
|
}
|
||
|
|
|
||
|
3 years ago
|
if d.logSelectorExpr == nil {
|
||
|
|
err := d.SetQuery(d.Query)
|
||
|
|
if err != nil {
|
||
|
|
level.Error(util_log.Logger).Log(
|
||
|
|
"msg", "failed to init log selector expr",
|
||
|
|
"delete_request_id", d.RequestID,
|
||
|
|
"user", d.UserID,
|
||
|
|
"err", err,
|
||
|
|
)
|
||
|
|
return false, nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
2 years ago
|
if !labels.Selector(d.matchers).Matches(entry.Labels) {
|
||
|
|
return false, nil
|
||
|
|
}
|
||
|
|
|
||
|
3 years ago
|
if d.StartTime <= entry.From && d.EndTime >= entry.Through && !d.logSelectorExpr.HasFilter() {
|
||
|
|
// Delete request covers the whole chunk and there are no line filters in the logSelectorExpr so the whole chunk will be deleted
|
||
|
|
return true, nil
|
||
|
|
}
|
||
|
|
|
||
|
4 years ago
|
ff, err := d.FilterFunction(entry.Labels)
|
||
|
|
if err != nil {
|
||
|
|
// The query in the delete request is checked when added to the table.
|
||
|
|
// So this error should not occur.
|
||
|
4 years ago
|
level.Error(util_log.Logger).Log(
|
||
|
|
"msg", "unexpected error getting filter function",
|
||
|
|
"delete_request_id", d.RequestID,
|
||
|
|
"user", d.UserID,
|
||
|
|
"err", err,
|
||
|
|
)
|
||
|
4 years ago
|
return false, nil
|
||
|
|
}
|
||
|
|
|
||
|
3 years ago
|
return true, ff
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
|
func intervalsOverlap(interval1, interval2 model.Interval) bool {
|
||
|
|
if interval1.Start > interval2.End || interval2.Start > interval1.End {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
return true
|
||
|
|
}
|