blob: 682b58af81eae6eec45c663d90556c7ad3383cda [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
# Filter language
This language allows us to filter logs based on a set of conditions. The language is designed to
map directly to UI chip elements.
## Examples
1. Show logs of severity info or higher for entries where the moniker is `core/ffx-laboratory:hello`
or the message contains “hello”.
```
(moniker:core/ffx-laboratory:hello | message:hello) severity:info
```
2. Show logs of severity error or higher, where the component url contains a package named hello,
where the manifest is named hello or where the message contains “hello world” but where the
message doesn’t contain “bye”.
```
(package_name:hello | manifest:hello | message:"hello world") severity:error !message:"bye"
```
3. Show logs from both core/foo and core/bar
```
moniker:core/foo|core/bar
# which is the same as:
moniker:core/foo|moniker:core/bar
```
4. Show logs where any field contains foo, bar or baz either in the message, url, moniker, etc.
```
any:foo|bar|baz
```
## Grammar
```
<operation> := <filter group> | <filter group> <and> <operation>
<filter group> := <single filter> | (<filter disjunction>)
<filter disjunction> := <single filter>
| <not> <single filter>
| <single filter> <or> <filter disjunction>
<single filter> := <category> <core operator> <strings>
| severity <operator> <severity>
| <pid tid category> <core operator> <numbers>
| <custom key> <operator> <custom key values>
| <string>
<core operator> := <contains> | <equality>
<operator> := <contains> | <equality> | <comparison>
<contains> := ':'
<equality> := '='
<comparison> := '>' | '<' | '>=' | '<='
<category> := moniker | tag | package_name | manifest | message | any
<pid tid category> := pid | tid
<custom key values> := <numbers> | <strings> | <boolean>
<severity> := trace | debug | info | warn | error | fatal
<boolean> := true | false
<strings> := <string> | <string> '|' <strings>
<string> := <chars> | "<chars with spaces>"
<chars> := any sequence of characters without space
<chars with spaces> := any sequence of characters with spaces
<numbers> := <number> | <number> '|' <numbers>
<number> := any sequence of numbers, including negative, exp, etc
<or> := '|' | or
<not> := not | '!'
<and> := and | '&' | ε # just writing a space will be interpreted as AND in the top level
```
The severities, logical operators, categories are case insensitive. When doing a `:` (contains)
operation, the value being matched against will be treated as case insensitive.
The following categories are supported:
1. Moniker: identifies the component that emitted the log.
2. Tag: a log entry might contain one or more tags in its metadata.
3. Package name: The name of the package present in the corresponding section of the url with
which the component was launched. Supported operations:
4. Manifest: The name of the manifest present in the corresponding section of the url with which
the component was launched.
5. Message: The log message.
6. Severity: The severity of the log.
7. Pid and Tid: the process and thread ids that emitted the log.
8. Custom key: A structured field contained in the payload of a structured log.
9. Any: Matches a value in any of the previous sections.
All categories support `=`(equals) and `:` (contains). The `:` operator will do substring search,
equality when dealing with numbers of booleans or a minimum severity operation when applied to
`severity`. Custom keys and severities also support `>`, `<`, `>=`, `<=`, `=`.
*/
Expression
= head:FilterGroup tail:(_ (And _)? FilterGroup)* {
return tail.reduce((result, element) => {
return result.concat([element[2]]);
}, [head]);
}
FilterGroup
= "(" _ head:Filter tail:(_ Or _ Filter)* _ ")" {
let group = tail.reduce((result, element) => {
return result.concat(element[3]);
}, [head])
return group;
}
/ filter:Filter { return [filter]; }
Filter
= Not _ single:SingleFilter {
single.not = true;
return single;
}
/ single:SingleFilter { return single; }
SingleFilter
= "severity"i operator:Operator severities:Severities {
return {
category: 'severity',
subCategory: undefined,
operator: operator,
values: severities,
not: false
};
}
/ category:Category operator:CoreOperator strings:Strings {
return {
category: category,
subCategory: undefined,
operator: operator,
values: strings,
not: false,
};
}
/ category:PidTidCategory operator:CoreOperator numbers:Numbers {
return {
category: category,
subCategory: undefined,
operator: operator,
values: numbers,
not: false,
}
}
/ customKey:CustomKey operator:Operator values:CustomKeyValues {
return {
category: 'custom',
subCategory: customKey,
operator: operator,
values: values,
not: false
}
}
/ string:String {
return {
category: 'any',
subCategory: undefined,
operator: 'contains',
values: [string],
not: false,
}
};
// NOTE: the returned types must match the ones in the Operator type in filter.ts.
CoreOperator
= ":" { return 'contains'; }
/ "=" { return 'equal'; }
// NOTE: the returned types must match the ones in the Operator type in filter.ts.
Operator
= ":" { return 'contains'; }
/ "=" { return 'equal'; }
/ ">=" { return 'greaterEq'; }
/ ">" { return 'greater'; }
/ "<=" { return 'lessEq'; }
/ "<" { return 'less'; }
// NOTE: the returned types must match the ones in the Category type in filter.ts.
Category
= "any"i
/ "custom"i
/ "manifest"i
/ "message"i
/ "moniker"i
/ "package-name"i
/ "tag"i;
PidTidCategory
= "pid"i
/ "tid"i;
CustomKeyValues
= Numbers
/ b:Boolean { return [b]; }
/ Strings
Severities
= head:Severity tail:("|" Severities)* {
return tail.reduce((result, element) => {
return result.concat(element[1]);
}, [head]);
}
// NOTE: the returned types must match the ones in the Severity type in filter.ts.
Severity
= "trace"i
/ "debug"i
/ "info"i
/ "warn"i
/ "error"i
/ "fatal"i
Boolean
= "true"i { return true; }
/ "false"i { return false; }
Strings
= head:String tail:("|" Strings)* {
return tail.reduce((result, element) => {
return result.concat(element[1]);
}, [head]);
}
CustomKey = [^:\\|\\(\\)><=" ]+ { return text(); }
Numbers
= head:Number tail:("|" Numbers)* {
return tail.reduce((result, element) => {
return result.concat(element[1]);
}, [head]);
}
// TODO(fxbug.dev/99486): support floating point, exponentials.
Number
= [0-9]+ { return parseInt(text(), 10); }
String
= [^"\\|\\(\\): ]+ { return text(); }
/ '"' [^"]+ '"' {
let t = text();
return t.slice(1, t.length - 1);
}
Not = "!" / "not"i
And = "&" / "and"i
Or = "|" / "or"i
// Whitespace
_ = [ \t\n\r]*