This commit is contained in:
Aliaksandr Valialkin 2024-05-22 17:41:45 +02:00
parent 79787ce25a
commit 2ff9cf9f43
No known key found for this signature in database
GPG key ID: 52C003EE2BCDB9EB
7 changed files with 68 additions and 17 deletions

View file

@ -1273,7 +1273,7 @@ See also:
### format pipe ### format pipe
`| format "pattern" as result_field` [pipe](#format-pipe) combines [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) `| format "pattern" as result_field` [pipe](#format-pipe) combines [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model)
according to the `pattern` and stores it to the `result_field`. according to the `pattern` and stores it to the `result_field`. All the other fields remain unchanged after the `| format ...` pipe.
For example, the following query stores `request from <ip>:<port>` text into [`_msg` field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#message-field), For example, the following query stores `request from <ip>:<port>` text into [`_msg` field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#message-field),
by substituting `<ip>` and `<port>` with the corresponding [log field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) names: by substituting `<ip>` and `<port>` with the corresponding [log field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) names:
@ -1289,12 +1289,12 @@ then `as _msg` part can be omitted. The following query is equivalent to the pre
_time:5m | format "request from <ip>:<port>" _time:5m | format "request from <ip>:<port>"
``` ```
If some field values must be put into double quotes before formatting, then add `:q` after the corresponding field name. If some field values must be put into double quotes before formatting, then add `q:` in front of the corresponding field name.
For example, the following command generates properly encoded JSON object from `_msg` and `stacktrace` [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) For example, the following command generates properly encoded JSON object from `_msg` and `stacktrace` [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model)
and stores it into `my_json` output field: and stores it into `my_json` output field:
```logsql ```logsql
_time:5m | format '{"_msg":<_msg:q>,"stacktrace":<stacktrace:q>}' as my_json _time:5m | format '{"_msg":<q:_msg>,"stacktrace":<q:stacktrace>}' as my_json
``` ```
See also: See also:
@ -1302,6 +1302,7 @@ See also:
- [Conditional format](#conditional-format) - [Conditional format](#conditional-format)
- [`extract` pipe](#extract-pipe) - [`extract` pipe](#extract-pipe)
#### Conditional format #### Conditional format
If the [`format` pipe](#format-pipe) musn't be applied to every [log entry](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model), If the [`format` pipe](#format-pipe) musn't be applied to every [log entry](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model),

View file

@ -29,6 +29,7 @@ type patternField struct {
type patternStep struct { type patternStep struct {
prefix string prefix string
field string field string
opt string
} }
func (ptn *pattern) clone() *pattern { func (ptn *pattern) clone() *pattern {
@ -154,6 +155,31 @@ func tryUnquoteString(s string) (string, int) {
} }
func parsePatternSteps(s string) ([]patternStep, error) { func parsePatternSteps(s string) ([]patternStep, error) {
steps, err := parsePatternStepsInternal(s)
if err != nil {
return nil, err
}
// Unescape prefixes
for i := range steps {
step := &steps[i]
step.prefix = html.UnescapeString(step.prefix)
}
// extract options part from fields
for i := range steps {
step := &steps[i]
field := step.field
if n := strings.IndexByte(field, ':'); n >= 0 {
step.opt = field[:n]
step.field = field[n+1:]
}
}
return steps, nil
}
func parsePatternStepsInternal(s string) ([]patternStep, error) {
if len(s) == 0 { if len(s) == 0 {
return nil, nil return nil, nil
} }
@ -163,7 +189,7 @@ func parsePatternSteps(s string) ([]patternStep, error) {
n := strings.IndexByte(s, '<') n := strings.IndexByte(s, '<')
if n < 0 { if n < 0 {
steps = append(steps, patternStep{ steps = append(steps, patternStep{
prefix: html.UnescapeString(s), prefix: s,
}) })
return steps, nil return steps, nil
} }
@ -199,10 +225,5 @@ func parsePatternSteps(s string) ([]patternStep, error) {
s = s[n+1:] s = s[n+1:]
} }
for i := range steps {
step := &steps[i]
step.prefix = html.UnescapeString(step.prefix)
}
return steps, nil return steps, nil
} }

View file

@ -205,6 +205,21 @@ func TestParsePatternStepsSuccess(t *testing.T) {
prefix: "&gt;", prefix: "&gt;",
}, },
}) })
f("<q:foo>bar<abc:baz:c:y>f<:foo:bar:baz>", []patternStep{
{
field: "foo",
opt: "q",
},
{
prefix: "bar",
field: "baz:c:y",
opt: "abc",
},
{
prefix: "f",
field: "foo:bar:baz",
},
})
} }

View file

@ -125,7 +125,7 @@ func parsePipeExtract(lex *lexer) (*pipeExtract, error) {
fromField: fromField, fromField: fromField,
ptn: ptn, ptn: ptn,
patternStr: patternStr, patternStr: patternStr,
iff: iff, iff: iff,
} }
return pe, nil return pe, nil

View file

@ -2,6 +2,7 @@ package logstorage
import ( import (
"fmt" "fmt"
"strconv"
"unsafe" "unsafe"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil" "github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
@ -136,7 +137,11 @@ func (shard *pipeFormatProcessorShard) formatRow(pf *pipeFormat, br *blockResult
if step.field != "" { if step.field != "" {
c := br.getColumnByName(step.field) c := br.getColumnByName(step.field)
v := c.getValueAtRow(br, rowIdx) v := c.getValueAtRow(br, rowIdx)
b = append(b, v...) if step.opt == "q" {
b = strconv.AppendQuote(b, v)
} else {
b = append(b, v...)
}
} }
} }
bb.B = b bb.B = b

View file

@ -39,6 +39,20 @@ func TestPipeFormat(t *testing.T) {
expectPipeResults(t, pipeStr, rows, rowsExpected) expectPipeResults(t, pipeStr, rows, rowsExpected)
} }
// plain string into a single field
f(`format '{"foo":<q:foo>,"bar":"<bar>"}' as x`, [][]Field{
{
{"foo", `"abc"`},
{"bar", `cde`},
},
}, [][]Field{
{
{"foo", `"abc"`},
{"bar", `cde`},
{"x", `{"foo":"\"abc\"","bar":"cde"}`},
},
})
// plain string into a single field // plain string into a single field
f(`format foo as x`, [][]Field{ f(`format foo as x`, [][]Field{
{ {
@ -95,7 +109,7 @@ func TestPipeFormat(t *testing.T) {
}) })
// format into existing field // format into existing field
f(`format "a<foo>aa<_msg>xx<a>x" as _msg`, [][]Field{ f(`format "a<foo>aa<_msg>xx<a>x"`, [][]Field{
{ {
{"_msg", `foobar`}, {"_msg", `foobar`},
{"a", "b"}, {"a", "b"},

View file

@ -233,11 +233,6 @@ func expectPipeResults(t *testing.T, pipeStr string, rows, rowsExpected [][]Fiel
t.Fatalf("unexpected error when parsing %q: %s", pipeStr, err) t.Fatalf("unexpected error when parsing %q: %s", pipeStr, err)
} }
pipeStrResult := p.String()
if pipeStrResult != pipeStr {
t.Fatalf("unexpected string representation for the pipe; got\n%s\nwant\n%s", pipeStrResult, pipeStr)
}
workersCount := 5 workersCount := 5
stopCh := make(chan struct{}) stopCh := make(chan struct{})
cancel := func() {} cancel := func() {}