mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-12 20:55:22 +01:00
app/vlselect: properly return live tailing results
This commit is contained in:
parent
dd62a2b9d6
commit
b26acec9a8
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputils"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputils"
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logstorage"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logstorage"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||||
)
|
)
|
||||||
@ -346,6 +347,10 @@ func ProcessLiveTailRequest(ctx context.Context, w http.ResponseWriter, r *http.
|
|||||||
|
|
||||||
end := time.Now().UnixNano()
|
end := time.Now().UnixNano()
|
||||||
doneCh := ctxWithCancel.Done()
|
doneCh := ctxWithCancel.Done()
|
||||||
|
flusher, ok := w.(http.Flusher)
|
||||||
|
if !ok {
|
||||||
|
logger.Panicf("BUG: it is expected that http.ResponseWriter (%T) supports http.Flusher interface", w)
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
start := end - tailOffsetNsecs
|
start := end - tailOffsetNsecs
|
||||||
end = time.Now().UnixNano()
|
end = time.Now().UnixNano()
|
||||||
@ -361,7 +366,10 @@ func ProcessLiveTailRequest(ctx context.Context, w http.ResponseWriter, r *http.
|
|||||||
httpserver.Errorf(w, r, "cannot get tail results for query [%q]: %s", q, err)
|
httpserver.Errorf(w, r, "cannot get tail results for query [%q]: %s", q, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
WriteJSONRows(w, resultRows)
|
if len(resultRows) > 0 {
|
||||||
|
WriteJSONRows(w, resultRows)
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-doneCh:
|
case <-doneCh:
|
||||||
@ -418,16 +426,12 @@ func (tp *tailProcessor) writeBlock(_ uint, timestamps []int64, columns []logsto
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure columns contain _time and _stream_id fields.
|
// Make sure columns contain _time field, since it is needed for proper tail work.
|
||||||
// These fields are needed for proper tail work.
|
|
||||||
hasTime := false
|
hasTime := false
|
||||||
hasStreamID := false
|
|
||||||
for _, c := range columns {
|
for _, c := range columns {
|
||||||
if c.Name == "_time" {
|
if c.Name == "_time" {
|
||||||
hasTime = true
|
hasTime = true
|
||||||
}
|
break
|
||||||
if c.Name == "_stream_id" {
|
|
||||||
hasStreamID = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !hasTime {
|
if !hasTime {
|
||||||
@ -435,11 +439,6 @@ func (tp *tailProcessor) writeBlock(_ uint, timestamps []int64, columns []logsto
|
|||||||
tp.cancel()
|
tp.cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !hasStreamID {
|
|
||||||
tp.err = fmt.Errorf("missing _stream_id field")
|
|
||||||
tp.cancel()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy block rows to tp.perStreamRows
|
// Copy block rows to tp.perStreamRows
|
||||||
for i, timestamp := range timestamps {
|
for i, timestamp := range timestamps {
|
||||||
@ -458,6 +457,7 @@ func (tp *tailProcessor) writeBlock(_ uint, timestamps []int64, columns []logsto
|
|||||||
streamID = value
|
streamID = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tp.perStreamRows[streamID] = append(tp.perStreamRows[streamID], logRow{
|
tp.perStreamRows[streamID] = append(tp.perStreamRows[streamID], logRow{
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
fields: fields,
|
fields: fields,
|
||||||
@ -477,15 +477,14 @@ func (tp *tailProcessor) getTailRows() ([][]logstorage.Field, error) {
|
|||||||
lastTimestamp, ok := tp.lastTimestamps[streamID]
|
lastTimestamp, ok := tp.lastTimestamps[streamID]
|
||||||
if ok {
|
if ok {
|
||||||
// Skip already written rows
|
// Skip already written rows
|
||||||
for i := range rows {
|
for len(rows) > 0 && rows[0].timestamp <= lastTimestamp {
|
||||||
if rows[i].timestamp > lastTimestamp {
|
rows = rows[1:]
|
||||||
rows = rows[i:]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resultRows = append(resultRows, rows...)
|
if len(rows) > 0 {
|
||||||
tp.lastTimestamps[streamID] = rows[len(rows)-1].timestamp
|
resultRows = append(resultRows, rows...)
|
||||||
|
tp.lastTimestamps[streamID] = rows[len(rows)-1].timestamp
|
||||||
|
}
|
||||||
}
|
}
|
||||||
clear(tp.perStreamRows)
|
clear(tp.perStreamRows)
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ VictoriaLogs provides `/select/logsql/tail?query=<query>` HTTP endpoint, which r
|
|||||||
e.g. it works in the way similar to `tail -f` unix command. For example, the following command returns live tailing logs with the `error` word:
|
e.g. it works in the way similar to `tail -f` unix command. For example, the following command returns live tailing logs with the `error` word:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl http://localhost:9428/select/logsql/tail -d 'query=error'
|
curl -N http://localhost:9428/select/logsql/tail -d 'query=error'
|
||||||
```
|
```
|
||||||
|
|
||||||
The `<query>` must conform the following restrictions:
|
The `<query>` must conform the following restrictions:
|
||||||
@ -143,16 +143,18 @@ The `<query>` must conform the following restrictions:
|
|||||||
[`uniq`](https://docs.victoriametrics.com/victorialogs/logsql/#uniq-pipe), [`top`](https://docs.victoriametrics.com/victorialogs/logsql/#top-pipe),
|
[`uniq`](https://docs.victoriametrics.com/victorialogs/logsql/#uniq-pipe), [`top`](https://docs.victoriametrics.com/victorialogs/logsql/#top-pipe),
|
||||||
[`unroll`](https://docs.victoriametrics.com/victorialogs/logsql/#unroll-pipe), etc. pipes.
|
[`unroll`](https://docs.victoriametrics.com/victorialogs/logsql/#unroll-pipe), etc. pipes.
|
||||||
|
|
||||||
- It must return [`_time`](https://docs.victoriametrics.com/victorialogs/keyconcepts/#time-field) and [`_stream_id`](https://docs.victoriametrics.com/victorialogs/keyconcepts/#stream-fields)
|
- It must return [`_time`](https://docs.victoriametrics.com/victorialogs/keyconcepts/#time-field) field. For example, this fields must be mentioned
|
||||||
fields, e.g. these fields must be left when using [`fields`](https://docs.victoriametrics.com/victorialogs/logsql/#fields-pipe),
|
in [`fields`](https://docs.victoriametrics.com/victorialogs/logsql/#fields-pipe) pipe if this pipe is used.
|
||||||
[`delete`](https://docs.victoriametrics.com/victorialogs/logsql/#delete-pipe) or [`rename`](https://docs.victoriametrics.com/victorialogs/logsql/#rename-pipe) pipes.
|
|
||||||
|
- It is recommended to return [`_stream_id`](https://docs.victoriametrics.com/victorialogs/keyconcepts/#stream-fields) field for more accurate live tailing
|
||||||
|
across multiple streams.
|
||||||
|
|
||||||
By default the `(AccountID=0, ProjectID=0)` [tenant](https://docs.victoriametrics.com/victorialogs/#multitenancy) is queried.
|
By default the `(AccountID=0, ProjectID=0)` [tenant](https://docs.victoriametrics.com/victorialogs/#multitenancy) is queried.
|
||||||
If you need querying other tenant, then specify it via `AccountID` and `ProjectID` http request headers. For example, the following query performs live tailing
|
If you need querying other tenant, then specify it via `AccountID` and `ProjectID` http request headers. For example, the following query performs live tailing
|
||||||
for `(AccountID=12, ProjectID=34)` tenant:
|
for `(AccountID=12, ProjectID=34)` tenant:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl http://localhost:9428/select/logsql/tail -H 'AccountID: 12' -H 'ProjectID: 34' -d 'query=error'
|
curl -N http://localhost:9428/select/logsql/tail -H 'AccountID: 12' -H 'ProjectID: 34' -d 'query=error'
|
||||||
```
|
```
|
||||||
|
|
||||||
The number of currently executed live tailing requests to `/select/logsql/tail` can be [monitored](https://docs.victoriametrics.com/victorialogs/#monitoring)
|
The number of currently executed live tailing requests to `/select/logsql/tail` can be [monitored](https://docs.victoriametrics.com/victorialogs/#monitoring)
|
||||||
|
@ -568,6 +568,21 @@ func (rwa *responseWriterWithAbort) WriteHeader(statusCode int) {
|
|||||||
rwa.sentHeaders = true
|
rwa.sentHeaders = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flush implements net/http.Flusher interface
|
||||||
|
func (rwa *responseWriterWithAbort) Flush() {
|
||||||
|
if rwa.aborted {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !rwa.sentHeaders {
|
||||||
|
rwa.sentHeaders = true
|
||||||
|
}
|
||||||
|
flusher, ok := rwa.ResponseWriter.(http.Flusher)
|
||||||
|
if !ok {
|
||||||
|
logger.Panicf("BUG: it is expected http.ResponseWriter (%T) supports http.Flusher interface", rwa.ResponseWriter)
|
||||||
|
}
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
// abort aborts the client connection associated with rwa.
|
// abort aborts the client connection associated with rwa.
|
||||||
//
|
//
|
||||||
// The last http chunk in the response stream is intentionally written incorrectly,
|
// The last http chunk in the response stream is intentionally written incorrectly,
|
||||||
@ -618,6 +633,7 @@ func Errorf(w http.ResponseWriter, r *http.Request, format string, args ...inter
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if rwa, ok := w.(*responseWriterWithAbort); ok && rwa.sentHeaders {
|
if rwa, ok := w.(*responseWriterWithAbort); ok && rwa.sentHeaders {
|
||||||
// HTTP status code has been already sent to client, so it cannot be sent again.
|
// HTTP status code has been already sent to client, so it cannot be sent again.
|
||||||
// Just write errStr to the response and abort the client connection, so the client could notice the error.
|
// Just write errStr to the response and abort the client connection, so the client could notice the error.
|
||||||
|
Loading…
Reference in New Issue
Block a user