Logs grouping
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) |
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})?
REST
%{REST} matches and discards all remaining tokens — use it to ignore trailing content:
%{IDENT:function} failed %{REST}
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' |
DATA | An unclassified segment (lexer fallback when no other type matches) | ??!!, \x1b[0m |
ANY | Any single token, regardless of type | 42, GET, foo |
REST | All remaining tokens | (greedy tail of the message) |
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 |
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 |
URI_PATH | File or URL path | /api/users, /var/log/syslog |
UUID | UUID string | 88da75f6-a07e-40b3-8c62-f2b28c505ff2 |
HASHTAG | Hashtag | #deploy, #uptrace |
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 |
GREEDYDATA | REST |
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"),
))
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.