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 }