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

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"sort"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/gorilla/websocket"
    14  
    15  	"github.com/grafana/loki/pkg/iter"
    16  	"github.com/grafana/loki/pkg/loghttp"
    17  	"github.com/grafana/loki/pkg/logproto"
    18  	"github.com/grafana/loki/pkg/logql"
    19  	logqllog "github.com/grafana/loki/pkg/logql/log"
    20  	"github.com/grafana/loki/pkg/util/log"
    21  	"github.com/grafana/loki/pkg/util/marshal"
    22  
    23  	"github.com/prometheus/prometheus/model/labels"
    24  	"github.com/weaveworks/common/user"
    25  )
    26  
    27  const (
    28  	defaultLabelKey          = "source"
    29  	defaultLabelValue        = "logcli"
    30  	defaultOrgID             = "logcli"
    31  	defaultMetricSeriesLimit = 1024
    32  	defaultMaxFileSize       = 20 * (1 << 20) // 20MB
    33  )
    34  
    35  var ErrNotSupported = errors.New("not supported")
    36  
    37  // FileClient is a type of LogCLI client that do LogQL on log lines from
    38  // the given file directly, instead get log lines from Loki servers.
    39  type FileClient struct {
    40  	r           io.ReadCloser
    41  	labels      []string
    42  	labelValues []string
    43  	orgID       string
    44  	engine      *logql.Engine
    45  }
    46  
    47  // NewFileClient returns the new instance of FileClient for the given `io.ReadCloser`
    48  func NewFileClient(r io.ReadCloser) *FileClient {
    49  	lbs := []labels.Label{
    50  		{
    51  			Name:  defaultLabelKey,
    52  			Value: defaultLabelValue,
    53  		},
    54  	}
    55  
    56  	eng := logql.NewEngine(logql.EngineOpts{}, &querier{r: r, labels: lbs}, &limiter{n: defaultMetricSeriesLimit}, log.Logger)
    57  	return &FileClient{
    58  		r:           r,
    59  		orgID:       defaultOrgID,
    60  		engine:      eng,
    61  		labels:      []string{defaultLabelKey},
    62  		labelValues: []string{defaultLabelValue},
    63  	}
    64  }
    65  
    66  func (f *FileClient) Query(q string, limit int, t time.Time, direction logproto.Direction, quiet bool) (*loghttp.QueryResponse, error) {
    67  	ctx := context.Background()
    68  
    69  	ctx = user.InjectOrgID(ctx, f.orgID)
    70  
    71  	params := logql.NewLiteralParams(
    72  		q,
    73  		t, t,
    74  		0,
    75  		0,
    76  		direction,
    77  		uint32(limit),
    78  		nil,
    79  	)
    80  
    81  	query := f.engine.Query(params)
    82  
    83  	result, err := query.Exec(ctx)
    84  	if err != nil {
    85  		return nil, fmt.Errorf("failed to exec query: %w", err)
    86  	}
    87  
    88  	value, err := marshal.NewResultValue(result.Data)
    89  	if err != nil {
    90  		return nil, fmt.Errorf("failed to marshal result data: %w", err)
    91  	}
    92  
    93  	return &loghttp.QueryResponse{
    94  		Status: "success",
    95  		Data: loghttp.QueryResponseData{
    96  			ResultType: value.Type(),
    97  			Result:     value,
    98  			Statistics: result.Statistics,
    99  		},
   100  	}, nil
   101  }
   102  
   103  func (f *FileClient) QueryRange(queryStr string, limit int, start, end time.Time, direction logproto.Direction, step, interval time.Duration, quiet bool) (*loghttp.QueryResponse, error) {
   104  	ctx := context.Background()
   105  
   106  	ctx = user.InjectOrgID(ctx, f.orgID)
   107  
   108  	params := logql.NewLiteralParams(
   109  		queryStr,
   110  		start,
   111  		end,
   112  		step,
   113  		interval,
   114  		direction,
   115  		uint32(limit),
   116  		nil,
   117  	)
   118  
   119  	query := f.engine.Query(params)
   120  
   121  	result, err := query.Exec(ctx)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	value, err := marshal.NewResultValue(result.Data)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	return &loghttp.QueryResponse{
   132  		Status: "success",
   133  		Data: loghttp.QueryResponseData{
   134  			ResultType: value.Type(),
   135  			Result:     value,
   136  			Statistics: result.Statistics,
   137  		},
   138  	}, nil
   139  }
   140  
   141  func (f *FileClient) ListLabelNames(quiet bool, start, end time.Time) (*loghttp.LabelResponse, error) {
   142  	return &loghttp.LabelResponse{
   143  		Status: loghttp.QueryStatusSuccess,
   144  		Data:   f.labels,
   145  	}, nil
   146  }
   147  
   148  func (f *FileClient) ListLabelValues(name string, quiet bool, start, end time.Time) (*loghttp.LabelResponse, error) {
   149  	i := sort.SearchStrings(f.labels, name)
   150  	if i < 0 {
   151  		return &loghttp.LabelResponse{}, nil
   152  	}
   153  
   154  	return &loghttp.LabelResponse{
   155  		Status: loghttp.QueryStatusSuccess,
   156  		Data:   []string{f.labelValues[i]},
   157  	}, nil
   158  }
   159  
   160  func (f *FileClient) Series(matchers []string, start, end time.Time, quiet bool) (*loghttp.SeriesResponse, error) {
   161  	m := len(f.labels)
   162  	if m > len(f.labelValues) {
   163  		m = len(f.labelValues)
   164  	}
   165  
   166  	lbs := make(loghttp.LabelSet)
   167  	for i := 0; i < m; i++ {
   168  		lbs[f.labels[i]] = f.labelValues[i]
   169  	}
   170  
   171  	return &loghttp.SeriesResponse{
   172  		Status: loghttp.QueryStatusSuccess,
   173  		Data:   []loghttp.LabelSet{lbs},
   174  	}, nil
   175  }
   176  
   177  func (f *FileClient) LiveTailQueryConn(queryStr string, delayFor time.Duration, limit int, start time.Time, quiet bool) (*websocket.Conn, error) {
   178  	return nil, fmt.Errorf("LiveTailQuery: %w", ErrNotSupported)
   179  }
   180  
   181  func (f *FileClient) GetOrgID() string {
   182  	return f.orgID
   183  }
   184  
   185  type limiter struct {
   186  	n int
   187  }
   188  
   189  func (l *limiter) MaxQuerySeries(userID string) int {
   190  	return l.n
   191  }
   192  
   193  type querier struct {
   194  	r      io.Reader
   195  	labels labels.Labels
   196  }
   197  
   198  func (q *querier) SelectLogs(_ context.Context, params logql.SelectLogParams) (iter.EntryIterator, error) {
   199  	expr, err := params.LogSelector()
   200  	if err != nil {
   201  		return nil, fmt.Errorf("failed to extract selector for logs: %w", err)
   202  	}
   203  	pipeline, err := expr.Pipeline()
   204  	if err != nil {
   205  		return nil, fmt.Errorf("failed to extract pipeline for logs: %w", err)
   206  	}
   207  	return newFileIterator(q.r, params, pipeline.ForStream(q.labels))
   208  }
   209  
   210  func (q *querier) SelectSamples(ctx context.Context, params logql.SelectSampleParams) (iter.SampleIterator, error) {
   211  	return nil, fmt.Errorf("Metrics Query: %w", ErrNotSupported)
   212  }
   213  
   214  func newFileIterator(
   215  	r io.Reader,
   216  	params logql.SelectLogParams,
   217  	pipeline logqllog.StreamPipeline,
   218  ) (iter.EntryIterator, error) {
   219  
   220  	lr := io.LimitReader(r, defaultMaxFileSize)
   221  	b, err := ioutil.ReadAll(lr)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  	lines := strings.FieldsFunc(string(b), func(r rune) bool {
   226  		return r == '\n'
   227  	})
   228  
   229  	if len(lines) == 0 {
   230  		return iter.NoopIterator, nil
   231  	}
   232  
   233  	streams := map[uint64]*logproto.Stream{}
   234  
   235  	processLine := func(line string) {
   236  		ts := time.Now()
   237  		parsedLine, parsedLabels, matches := pipeline.ProcessString(ts.UnixNano(), line)
   238  		if !matches {
   239  			return
   240  		}
   241  
   242  		var stream *logproto.Stream
   243  		lhash := parsedLabels.Hash()
   244  		var ok bool
   245  		if stream, ok = streams[lhash]; !ok {
   246  			stream = &logproto.Stream{
   247  				Labels: parsedLabels.String(),
   248  			}
   249  			streams[lhash] = stream
   250  		}
   251  
   252  		stream.Entries = append(stream.Entries, logproto.Entry{
   253  			Timestamp: ts,
   254  			Line:      parsedLine,
   255  		})
   256  	}
   257  
   258  	if params.Direction == logproto.FORWARD {
   259  		for _, line := range lines {
   260  			processLine(line)
   261  		}
   262  	} else {
   263  		for i := len(lines) - 1; i >= 0; i-- {
   264  			processLine(lines[i])
   265  		}
   266  	}
   267  
   268  	if len(streams) == 0 {
   269  		return iter.NoopIterator, nil
   270  	}
   271  
   272  	streamResult := make([]logproto.Stream, 0, len(streams))
   273  
   274  	for _, stream := range streams {
   275  		streamResult = append(streamResult, *stream)
   276  	}
   277  
   278  	return iter.NewStreamsIterator(
   279  		streamResult,
   280  		params.Direction,
   281  	), nil
   282  }