Grouping rules
Grouping rules let you change how Uptrace groups logs and exceptions together. Each rule is a Grok-style pattern: literal words plus typed placeholders that extract variable parts (numbers, IPs, identifiers, etc.) and feed them into the grouping fingerprint.
For example, you can configure Uptrace to create a separate error group for each unknown PostgreSQL column:
# Error messages
ERROR: column "event.created_at" does not exist (SQLSTATE=42703)
ERROR: column "updated_at" does not exist (SQLSTATE=42703)
ERROR: column "name" does not exist (SQLSTATE=42703)
# Pattern
%{LOG_LEVEL:log_severity} column %{QUOTED:#column} does not exist %{ATTR:sqlstate}
Patterns
A pattern is a sequence of matchers separated by whitespace. Each matcher is either a literal word or a typed placeholder.
Literals
Plain words match tokens exactly:
error connecting to database
Typed placeholders
Typed placeholders use Grok syntax %{TYPE} to match variable tokens by type:
%{NUMBER}
%{IP}
%{LOG_LEVEL}
Capture name
Add a capture name after a colon to extract the matched value into an attribute:
%{NUMBER:status_code}
%{IP:remote_addr}
Prefix the capture name with # to also include the matched value in the grouping fingerprint hash. This is how you create a separate group per unique value:
%{IDENT:#function_name}
Without a capture name, use the fingerprint option instead:
%{IDENT,fingerprint}
Primary constraint
The parenthesized argument %{TYPE(arg)} constrains which tokens match. Its meaning depends on the type:
- Value constraint (most types): match only tokens with this exact value.text
%{LOG_LEVEL(ERROR)} %{LOG_LEVEL(ERROR):level} - Key constraint (
ATTR): match onlykey=valuetokens whose key equals the argument. The key auto-captures, so%{ATTR(status)}is equivalent to%{ATTR(status):status}. Use_to suppress auto-capture.text%{ATTR(status)} %{ATTR(user_id):var} %{ATTR(status):_}
Options
Options are comma-separated key=value pairs after the capture name:
%{NUMBER:duration,unit=seconds}
| Option | Description |
|---|---|
unit | Attach a unit to numeric values for normalization |
fingerprint | Include the matched value in the grouping fingerprint hash (no value) |
extract | Backtick-quoted Go regex applied to a %{QUOTED} token; named groups become captures |
Supported units (used with unit=...):
- Time:
nanoseconds,microseconds(us),milliseconds(ms),seconds(s) - Storage:
bytes(by),kilobytes(kb),megabytes(mb),gigabytes(gb),terabytes(tb) - Other:
percents(%),count,celsius,volts,amperes,joules,grams
Regex extraction (extract option)
On %{QUOTED} matchers you can supply a Go regex in backticks. Named groups in the regex become additional captures at runtime:
ERROR %{QUOTED:msg,extract=`(?P<name>\w+) is (?P<age>\d+)`}
Given the log line ERROR "Alice is 25", this rule captures msg=Alice is 25, name=Alice, and age=25.
If the regex does not match the quoted token, the rule still fires and the outer :capture is still recorded, but no extra attributes are emitted. Optional named groups that do not participate in a match are not emitted as empty attributes. If a named group collides with the matcher's own :capture or another attribute key, a numeric suffix is appended (e.g., the second value becomes name1).
Optional matchers
Append ? to make a matcher optional — the pattern still matches if the token is absent:
error code %{NUMBER:code}? occurred
This matches both error code 500 occurred and error code occurred.
Groups and alternatives
Parentheses define a group of alternatives separated by |:
(%{LOG_LEVEL:level}|%{WORD:level}) %{WORD:msg}
Groups themselves can be optional:
(%{LOG_LEVEL})?
Repeat matchers (%{ANY}+ / %{ANY}*)
Append + or * to %{ANY} to match multiple tokens of any type:
%{ANY}+matches one or more tokens.%{ANY}*matches zero or more tokens.
Repeat matchers work anywhere in a pattern:
error %{WORD:action} %{ANY:details}+ matches "error connect failed with timeout"
foo %{ANY:mid}+ bar matches "foo x y z bar"
%{IDENT:function} failed %{ANY}+ matches "myFunc failed with status 500"
Repeat is only valid on %{ANY}. It cannot be combined with a value constraint or a unit option.
Available types
Some types are virtual — they expand to several concrete types.
| Virtual | Expands to |
|---|---|
ANY | Any single token, regardless of type |
NUMBER | INT, FLOAT, BYTE_SIZE, TRACE_ID_HEX |
IP | IPV4, IPV6 |
IDENT | WORD, IDENT |
TIMESTAMP | ISO8601_DATE, UNIX_DATE, HTTP_DATE, SYSLOG_DATE, DATETIME |
Text
| Type | Description | Example |
|---|---|---|
WORD | A single alphabetical word | error, database |
IDENT | An identifier | user_id, MyClass, obj.attr |
QUOTED | A quoted string | "hello world", 'foo' |
UNKNOWN | An unclassified segment (lexer fallback when no other type matches) | ??!!, \x1b[0m |
ANY | Any single token. With +: one or more tokens. With *: zero or more tokens. | 42, GET, foo |
Numeric
| Type | Description | Example |
|---|---|---|
NUMBER | Any numeric (INT, FLOAT, BYTE_SIZE, TRACE_ID_HEX) | 42, 3.14, 10KB |
INT | Integer | 200, -17 |
FLOAT | Floating-point number | 3.14, -0.5, 1e9 |
BYTE_SIZE | Byte size with unit | 10KB, 2.5MiB, 512B |
TRACE_ID_HEX | 32-character hex string | 5d41402abc4b2a76b9719d911017c592 (MD5) |
Network
| Type | Description | Example |
|---|---|---|
IP | IPV4 or IPV6 (virtual) | 127.0.0.1, ::1 |
IPV4 | IPV4 address | 192.168.1.1 |
IPV6 | IPV6 address | 2001:db8::1 |
HOST_PORT | host:port combination | 10.0.0.1:443 |
MAC | MAC address | 00:1A:2B:3C:4D:5E |
EMAIL | Email address | admin@example.com |
URI | Full URI | https://example.com/api/v1 |
Temporal
| Type | Description | Example |
|---|---|---|
TIMESTAMP | Any timestamp format (virtual) | 2024-01-15T14:30:00Z |
ISO8601_DATE | ISO8601 / RFC3339 | 2024-01-15T14:30:00Z |
UNIX_DATE | Unix date | Mon Jan 2 15:04:05 MST 2006 |
HTTP_DATE | HTTP log date | 21/Nov/2024:14:20:00 +0000 |
SYSLOG_DATE | Syslog timestamp | Jan 2 15:04:05 |
DATETIME | Date and time | 2024-01-15 14:30:00 |
DATE | Date only | 2024-01-15 |
TIME | Time of day | 14:30:00 |
MONTH_NAME | Month name | Jan, February |
WEEKDAY | Day-of-week name | Mon, Tuesday |
Structured
| Type | Description | Example |
|---|---|---|
JSON | JSON object or array | {"foo": "bar"}, [1, 2, 3] |
ATTR | key=value attribute | status=200, user_id=42 |
System
| Type | Description | Example |
|---|---|---|
LOG_LEVEL | Log severity level | INFO, WARN, ERROR |
HTTP_METHOD | HTTP method | GET, POST, DELETE |
HTTP_VERSION | HTTP protocol version | HTTP/1.1, HTTP/2.0 |
HTTP_STATUS | HTTP status code with reason phrase | 200 OK, 404 Not Found |
URI_PATH | File or URL path | /api/users, /var/log/syslog |
UUID | UUID string | 88da75f6-a07e-40b3-8c62-f2b28c505ff2 |
HASHTAG | Hashtag | #deploy, #uptrace |
HTML_TAG | HTML tag | <html>, </div>, <br/> |
Type aliases
For compatibility with existing Grok corpora, several types have alternative names:
| Alias | Canonical |
|---|---|
STRING | QUOTED |
NUM | NUMBER |
INTEGER | INT |
TIMESTAMP_ISO8601 | ISO8601_DATE |
SYSLOGTIMESTAMP | SYSLOG_DATE |
Fingerprints
Uptrace groups similar logs and exceptions by hashing certain parts of the message. By default it only hashes literal words; use # (or the fingerprint option) to include captured values in the hash.
For example:
unknown column: %{WORD:#column}
The pattern above creates a separate group for each column, which is useful for alerting:
# Group 1
unknown column: foo
unknown column: foo
# Group 2
unknown column: bar
unknown column: bar
You can also set the grouping.fingerprint attribute when creating logs and exceptions, which overrides the automatically derived fingerprint:
span := trace.SpanFromContext(ctx)
span.AddEvent("exception", trace.WithAttributes(
attribute.String("exception.type", "*exec.ExitError"),
attribute.String("exception.message", "exit status 1"),
attribute.String("grouping.fingerprint", "exec.ExitError"),
))
Debugging rules
To test rules before they change live groups, open Logs & Errors, click Grouping rules in the app bar, then Debug. Select the rules to run, write a new one if needed, and add log lines by typing them or by picking them from the project's logs. The results show the group each line lands in, the part of the rule that matched each piece of the line, and the lines no rule matched. Try demo mode loads editable lessons with sample rules and lines.
Examples
Go-style error messages:
# Messages
strconv.ParseInt failed
SendEmail failed
mypkg.MyFunc failed
# Pattern
%{IDENT:#code_function} failed
PostgreSQL unknown column errors:
# Error messages
ERROR: column "event.created_at" does not exist (SQLSTATE=42703)
ERROR: column "updated_at" does not exist (SQLSTATE=42703)
ERROR: column "name" does not exist (SQLSTATE=42703)
# Pattern
%{LOG_LEVEL:log_severity} column %{QUOTED:#column} does not exist %{ATTR:sqlstate}
A single grouping rule may declare multiple patterns — any of them matching is enough:
can't find item %{NUMBER:item_id}
can not find item %{NUMBER:item_id}
%{NUMBER:item_id} not found
Conclusion
Grouping rules work best with structured logs and are not a replacement for the log parsers provided by OpenTelemetry Logs and Vector.