Step 4: Multi-Format Detection
In a real-world system, you often receive logs in various formats through a single input. This final step teaches you how to build a unified parser that can intelligently detect the format of each message and route it to the correct parsing logic.
The Goal
You will build a single pipeline that parses JSON, five-field CSV, syslog, and Access Log strings.
The "Detect -> Route" Pattern
- Detect: A
mappingprocessor inspects the raw log string and uses simple heuristics (like checking for{or<) to guess the format, setting a metadata field likedetected_format. - Route: A
switchprocessor reads this metadata field and selects a Bloblang mapping using.parse_json(),.parse_csv(), or regular-expression captures.
Implementation
-
Create the Unified Parser: Copy the following configuration into a file named
unified-parser.yaml. This pipeline combines the logic from the previous steps.Complete pipelineunified-parser.yamlname: unified-log-parser
description: A pipeline that detects and parses multiple log formats.
config:
input:
http_server:
address: 0.0.0.0:8080
path: /ingest
pipeline:
processors:
# 1. Detect the format from the raw log field.
- mapping: |
root = this
let content = this.raw_log.string()
meta detected_format = if $content.has_prefix("{") {
"json"
} else if $content.has_prefix("<") {
"syslog"
} else if $content.split(",").length() >= 3 {
"csv"
} else if $content.re_match("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}") {
"access_log"
} else {
"unknown"
}
# 2. Route to the matching parser.
- switch:
- check: meta("detected_format") == "json"
processors:
- mapping: root = this.raw_log.parse_json()
- mapping: |
root = this
root.parsed_by = "json_parser"
- check: meta("detected_format") == "csv"
processors:
- mapping: |
let header = "timestamp,metric_name,sensor_id,value,unit\n"
root = ($header + this.raw_log).parse_csv().index(0)
root.parsed_by = "csv_parser"
- check: meta("detected_format") == "syslog"
processors:
- mapping: |
let parts = this.raw_log.re_find_all_submatch("^<([0-9]+)>([A-Z][a-z]{2} +[0-9]+ [0-9:]+) ([^ ]+) ([^ :\\[]+)(?:\\[([0-9]+)\\])?: (.*)$").index(0)
root = {
"priority": $parts.index(1),
"timestamp": $parts.index(2),
"hostname": $parts.index(3),
"tag": $parts.index(4),
"pid": $parts.index(5),
"message": $parts.index(6),
"parsed_by": "syslog_parser"
}
- check: meta("detected_format") == "access_log"
processors:
- mapping: |
let parts = this.raw_log.re_find_all_submatch("^(\\S+) \\S+ \\S+ \\[([^]]+)\\] \"([^\"]+)\" ([0-9]+) ([0-9]+|-)$").index(0)
root = {
"ip": $parts.index(1),
"timestamp": $parts.index(2),
"request": $parts.index(3),
"status": $parts.index(4).number(),
"bytes": if $parts.index(5) == "-" { null } else { $parts.index(5).number() },
"parsed_by": "access_log_parser"
}
- processors:
- mapping: |
root = this
root.parsed_by = "unknown"
output:
stdout:
codec: lines -
Deploy and Test:
# --- Send logs in different formats ---
curl -X POST http://localhost:8080/ingest -d '{"raw_log": "{\"level\":\"info\"}"}'
curl -X POST http://localhost:8080/ingest \
-d '{"raw_log": "2025-10-20T14:23:45Z,temperature,temp-sensor-01,35.5,celsius"}'
curl -X POST http://localhost:8080/ingest -d '{"raw_log": "127.0.0.1 - - [01/Jan/2025:12:00:00 +0000] \"GET / HTTP/1.1\" 200 123"}' -
Verify: Check your logs. You will see three structured messages. Each one will have a
parsed_byfield indicating that it was correctly routed to and processed by the right set of processors.
You have now built a parser for the three configured log formats.