ELK Stack β Dissect Filter
What Is Dissect?
Dissect is a delimiter-based log parser that splits a string into fields using a fixed pattern of delimiters. Unlike Grok (which uses regular expressions), Dissect is a tokenizer β it simply splits on exact character sequences.
This makes Dissect:
- β‘ 5β10x faster than Grok
- π Easier to read β no regex to decode
- β Predictable β no regex backtracking issues
- β οΈ Less flexible β only works when the log format has consistent delimiters
Dissect is available in:
- Logstash β
dissectfilter plugin - Elasticsearch β
dissectingest processor
Dissect Syntax
The basic dissect pattern uses %{field_name} to define fields, separated by literal delimiter characters:
%{field1} %{field2} %{field3}
The characters between %{...} tokens are treated as literal delimiters that must appear exactly in the log.
Example:
Log: 192.168.1.50 GET /api/users 200
Pattern: %{ip} %{method} %{path} %{status}
Result: ip=192.168.1.50, method=GET, path=/api/users, status=200
Dissect Modifiers
Dissect provides modifiers to handle more complex parsing scenarios:
| Modifier | Syntax | Description |
|---|---|---|
| Skip | %{?skip_field} |
Match but discard the value |
| Append | %{+field} |
Append to an existing field |
| Append with order | %{+field/N} |
Append in a specific order (N = position) |
| Indirect | %{&field} |
Use the value of another field as the field name |
| Right padding | %{field->} |
Consume trailing whitespace/padding |
Skip Modifier β %{?name}
Discard fields you donβt need:
Raw log:
2026-04-01 10:15:30 INFO web01 GET /api/users 200
Dissect pattern:
%{timestamp} %{+timestamp} %{level} %{?host} %{method} %{path} %{status}
The %{?host} matches the hostname but discards it.
Append Modifier β %{+name}
Join multiple tokens into a single field:
Raw log:
2026-04-01 10:15:30 INFO Request processed
Dissect pattern:
%{timestamp} %{+timestamp} %{level} %{message}
Parsed output:
{
"timestamp": "2026-04-01 10:15:30",
"level": "INFO",
"message": "Request processed"
}
The %{timestamp} and %{+timestamp} values are joined with a space to form "2026-04-01 10:15:30".
Ordered Append β %{+name/N}
Control the order of appended values:
Raw log:
30 2026-04-01 10:15 INFO
Dissect pattern:
%{+timestamp/3} %{+timestamp/1} %{+timestamp/2} %{level}
Parsed output:
{
"timestamp": "2026-04-01 10:15 30",
"level": "INFO"
}
Tokens are joined in order: /1 β /2 β /3.
Indirect Modifier β %{&name}
Use a captured value as a field name:
Raw log:
env=production region=us-east-1 status=healthy
Dissect pattern:
%{?key1}=%{&key1} %{?key2}=%{&key2} %{?key3}=%{&key3}
Parsed output:
{
"env": "production",
"region": "us-east-1",
"status": "healthy"
}
The %{?key1} captures env (and discards it as a value), then %{&key1} uses env as the field name for production.
Right Padding β %{field->}
Consume extra whitespace or repeated delimiters:
Raw log (variable spacing):
192.168.1.50 GET /api/users 200
Dissect pattern:
%{ip->} %{method->} %{path->} %{status}
The -> modifier consumes all trailing whitespace after each field until the next non-whitespace delimiter is found.
Using Dissect in Logstash
Basic Dissect Filter
filter {
dissect {
mapping => {
"message" => "%{timestamp} %{+timestamp} %{level} %{logger} - %{log_message}"
}
}
}
With Convert Datatype
filter {
dissect {
mapping => {
"message" => "%{ip} - %{user} [%{timestamp}] \"%{method} %{path} HTTP/%{http_version}\" %{status} %{bytes}"
}
convert_datatype => {
"status" => "int"
"bytes" => "int"
}
}
}
Raw log:
192.168.1.50 - admin [01/Apr/2026:10:15:30 +0000] "GET /api/users HTTP/1.1" 200 1234
Parsed output:
{
"ip": "192.168.1.50",
"user": "admin",
"timestamp": "01/Apr/2026:10:15:30 +0000",
"method": "GET",
"path": "/api/users",
"http_version": "1.1",
"status": 200,
"bytes": 1234
}
Using Dissect in Elasticsearch Ingest Pipeline
PUT _ingest/pipeline/dissect-access-log
{
"description": "Parse access logs with dissect",
"processors": [
{
"dissect": {
"field": "message",
"pattern": "%{ip} - %{user} [%{timestamp}] \"%{method} %{path} HTTP/%{http_version}\" %{status} %{bytes}"
}
},
{
"convert": {
"field": "status",
"type": "integer"
}
},
{
"convert": {
"field": "bytes",
"type": "integer"
}
},
{
"date": {
"field": "timestamp",
"formats": ["dd/MMM/yyyy:HH:mm:ss Z"],
"target_field": "@timestamp"
}
}
]
}
Dissect vs Grok β Performance Comparison
| Aspect | Dissect | Grok |
|---|---|---|
| Parsing method | Delimiter-based tokenizer | Regular expressions |
| Speed | β‘ 5β10x faster | π’ Slower (regex engine) |
| CPU usage | Low | Higher (backtracking) |
| Flexibility | Fixed delimiters only | Any regex pattern |
| Variable whitespace | Needs -> modifier |
Handled by \s+ |
| Optional fields | Not supported | Supported with (?:...)? |
| Pattern reuse | Not composable | Composable patterns |
| Failure mode | Hard fail if format doesnβt match | Partial matches possible |
When to Use Dissect
- β Log lines have consistent delimiters (spaces, tabs, pipes, brackets)
- β High throughput β parsing millions of events per second
- β Format is predictable β no optional fields or variable formats
- β You want simpler patterns that are easy to maintain
When to Use Grok
- β Logs have variable formats or optional fields
- β You need regex matching (e.g., email addresses, UUIDs)
- β Format is semi-structured with no consistent delimiters
- β You need to reuse patterns across multiple configs
Combining Dissect + Grok
For complex logs, use dissect first (fast) to extract the major fields, then grok (slower) only on the parts that need regex:
filter {
# Step 1: Fast dissect for the structured prefix
dissect {
mapping => {
"message" => "[%{timestamp}] [%{level}] [%{service}] %{detail}"
}
}
# Step 2: Grok only on the unstructured detail field
grok {
match => {
"detail" => "%{WORD:method} %{URIPATH:path} %{NUMBER:status:int} %{NUMBER:duration:int}ms(?: error=%{GREEDYDATA:error})?"
}
tag_on_failure => ["_grok_detail_fail"]
}
}
Raw log:
[2026-04-01T10:15:30.456Z] [ERROR] [PaymentService] POST /api/payments 500 2345ms error=InsufficientFunds: balance=0.00
Parsed output:
{
"timestamp": "2026-04-01T10:15:30.456Z",
"level": "ERROR",
"service": "PaymentService",
"method": "POST",
"path": "/api/payments",
"status": 500,
"duration": 2345,
"error": "InsufficientFunds: balance=0.00"
}
This hybrid approach gives you the speed of dissect for the structured parts and the flexibility of grok for the variable parts.
Quick Reference
# Dissect syntax
%{field} # Capture a field
%{?skip} # Match and discard
%{+append} # Append to existing field
%{+append/2} # Append with order
%{&indirect} # Use value as field name
%{field->} # Consume right padding
# Logstash dissect filter
filter {
dissect {
mapping => {
"message" => "%{ip} %{method} %{path} %{status}"
}
convert_datatype => {
"status" => "int"
}
}
}
# ES ingest dissect processor
{
"dissect": {
"field": "message",
"pattern": "%{ip} %{method} %{path} %{status}"
}
}
# Combining dissect + grok
dissect β extract major fields (fast)
grok β parse remaining detail (flexible)
Previous: Custom Grok Patterns β Β· Next: Multiline Logs β