Parsers
Parsers convert unstructured raw logs into structured ParserSchema objects that downstream detectors consume.
| Schema | Description | |
|---|---|---|
| Input | LogSchema | Unstructured log |
| Output | ParserSchema | Structured log |
This document explains expected APIs, how to implement a parser, testing tips and common pitfalls.
Overview
- Parsers must inherit from
CoreParserand provide aparse()implementation. CoreParser.run()handles lifecycle and callsparse()for each input; implement pure parsing logic insideparse()where possible.- Use a typed
Configclass (subclass ofCoreParserConfig) to hold runtime parameters.
CoreParser: minimal API
Recommended signatures and behavior:
class CoreParser:
def run(self, input_: schemas.LogSchema, output_: schemas.ParserSchema) -> bool:
"""Top-level runner. Calls parse() and performs pre/post processing.
Return True when a parsed output was produced, False otherwise.
"""
def parse(self, input_: schemas.LogSchema, output_: schemas.ParserSchema) -> bool | None:
"""Implement parsing here.
- Fill the required output_ fields (see "ParserSchema: what to populate" below).
- Return True, or None, to emit output_ as the parsed log.
- Return False to drop the log: process() then returns None for it.
"""
def train(self, input_: schemas.LogSchema) -> None:
"""Optional: train internal models. Can be a no-op for stateless parsers."""
ParserSchema: what to populate
parse() only has to fill the fields that describe the match:
eventID(int): identifier for the matched template/eventtemplate(string): event template textvariables(repeated string): extracted parameters (extend the list)
CoreParser.run(), which calls parse(), fills in everything else for you:
parserID,parserType: the parser's name andmethod_typeparsedLogID: a new unique ID for the parsed recordlogID,log: copied from the inputLogSchemaheaderVariables: the header fields extracted withlog_formatreceivedTimestamp,parsedTimestamp: set just before and just afterparse()runs
Don't set these in parse(): run() writes parsedTimestamp after parse()
returns, so a value set there is overwritten.
Creating a new parser: step by step
- Create a Config class inheriting
CoreParserConfig. - Create parser class inheriting
CoreParser. - Implement
parse()to populateoutput_. - Add unit tests for
parse()behavior.
Example:
# filepath: src/detectmatelibrary/parsers/my_parser.py
from typing import Any
from detectmatelibrary.common.parser import CoreParser, CoreParserConfig
from detectmatelibrary import schemas
class MyParserConfig(CoreParserConfig):
method_type: str = "my_parser"
# add parser-specific settings here
pattern: str | None = None
class MyParser(CoreParser):
def __init__(
self,
name: str = "MyParser",
config: MyParserConfig | dict[str, Any] = MyParserConfig(),
):
if isinstance(config, dict):
config = MyParserConfig.from_dict(config, name)
super().__init__(name=name, config=config)
def parse(self, input_: schemas.LogSchema, output_: schemas.ParserSchema) -> bool:
text = input_.log or ""
# simple example: split on whitespace and treat as variables
tokens = text.split()
output_["eventID"] = 1
output_["template"] = " ".join(["<*>"] * len(tokens))
output_["variables"].extend(tokens)
return True
Testing parsers
Unit test parse() directly using ParserSchema objects:
def test_my_parser_parse():
from detectmatelibrary.parsers.my_parser import MyParser
from detectmatelibrary import schemas
parser = MyParser()
raw = schemas.LogSchema({"log": "a b c", "logID": "1"})
out = schemas.ParserSchema()
ok = parser.parse(raw, out)
assert ok is True
assert out["eventID"] == 1
assert out["variables"] == ["a", "b", "c"]
Available parsers
- JSON Parser: extracts structured fields from JSON-formatted logs.
- Template Matcher: matches logs against a predefined set of
<*>templates. - Template Tree Matcher: matches logs against a predefined set of
<*>templates using a tree structure. - LogBatcher Parser: LLM-based parser that infers templates from raw logs with no training data.
- Drain parser: Parser inspired by Drain Publication.
- Auto parser: The auto parser uses a brute-force strategy: it iterates through every log-type record in the internal dataset and chooses the regex and templates that best matches the provided logs.
Common parameters (all parsers)
There are some parameters, that every parser inherits from CoreParserConfig/CoreConfig/BasicConfig, regardless of what it does. The other parameters, that are specific for the respective parser, are explained right at the parsers documentation page, later on.
Top level
| Field | Type | Default | Description |
|---|---|---|---|
auto_config |
boolean | False | Runs the configuration step before the training process. |
params
| Field | Type | Default | Description |
|---|---|---|---|
start_id |
integer | 10 | Number used to start the unique ID generator. |
data_use_training |
integer, null | None | Number of logs used for training. None skips training. |
data_use_configure |
integer, null | None | Number of logs used for the configuration step. None skips it. |
use_config_data_as_training |
boolean | True | Combine the configured data in the training process if True. |
train_buffer_max_records |
integer | 100000 | Configure records kept in memory for training (use_config_data_as_training) before the buffer spills to Parquet files on disk, in parts of this many records. |
train_buffer_dir |
string, null | None | Local directory for the spilled training buffer. None uses the system temp directory (TMPDIR). Each spill goes to a private detectmate-train-* directory, removed after training reads it; a killed process leaves it behind. |
log_format |
string, null | None | fitting description yet to find |
time_format |
string, null | None | fitting description yet to find |