-
Notifications
You must be signed in to change notification settings - Fork 2
Feat/parser dp #240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Feat/parser dp #240
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
b36da11
add temporally a different version of detectmateperformance
ipmach edf9881
first drain version
ipmach 577596b
working in drain
ipmach 0108f66
Merge branch 'development' into feat/parser_dp
ipmach 99b7991
add allow reset or not train data
ipmach bed8649
add drain documentation
ipmach 7b325d3
combinatons class
ipmach eed0df8
adding autoconfig
ipmach f1f4712
small reformat
ipmach c1358f0
remove print
ipmach 8c51359
update detectmateperformance
ipmach 2df2394
update merge issues
ipmach 4d98181
update docs
ipmach ac4554b
Correct capitalization in Drain parser description
ipmach 646ba53
Refine language and formatting in drain_parser.md
ipmach File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| # flake8: noqa | ||
|
|
||
| # --8<-- [start:example_1] | ||
| from detectmatelibrary.parsers.drain import DrainParser | ||
| from detectmatelibrary import schemas | ||
|
|
||
| # instantiate parser (config can be a dict or a config object) | ||
| config_dict = { | ||
| "parsers": { | ||
| "DrainParser": { | ||
| "method_type": "drain_parser", | ||
| "data_use_training": 2, | ||
| "reset_in_post_train": False, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| parser = DrainParser(config=config_dict) | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, general kenobi!"})) | ||
| print(parsed["template"]) # "templates not yet generated" | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, captain kenobi!"})) | ||
| print(parsed["template"]) # "templates not yet generated" | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, sargent kenobi!"})) | ||
| print(parsed["template"]) # "hello there <*> kenobi" | ||
|
|
||
| parser.update_state("keep_training") | ||
| parser.process(schemas.LogSchema({"log": "bella ciao bella ciao"})) | ||
| parser.update_state("stop_training") | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, sargent kenobi!"})) | ||
| print(parsed["template"]) # "hello there <*> kenobi" | ||
| # --8<-- [end:example_1] | ||
|
|
||
|
|
||
| # --8<-- [start:example_2] | ||
| from detectmatelibrary.parsers.drain import DrainParser | ||
| from detectmatelibrary import schemas | ||
|
|
||
| # instantiate parser (config can be a dict or a config object) | ||
| config_dict = { | ||
| "parsers": { | ||
| "DrainParser": { | ||
| "method_type": "drain_parser", | ||
| "data_use_training": 2, | ||
| "reset_in_post_train": False, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| parser = DrainParser(config=config_dict) | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, general kenobi!"})) | ||
| print(parsed["template"]) # "templates not yet generated" | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, captain kenobi!"})) | ||
| print(parsed["template"]) # "templates not yet generated" | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, sargent kenobi!"})) | ||
| print(parsed["template"]) # "hello there <*> kenobi" | ||
|
|
||
| parser.update_state("keep_training") | ||
| parser.process(schemas.LogSchema({"log": "bella ciao bella ciao"})) | ||
| parser.update_state("stop_training") | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, sargent kenobi!"})) | ||
| print(parsed["template"]) # "template not found" | ||
| # --8<-- [end:example_2] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| # Drain parser | ||
|
|
||
| The parser is derived from the official [Drain publication](https://ieeexplore.ieee.org/document/8029742). | ||
|
|
||
| It also wraps functionality from the DetectMatePerformance project: https://github.com/ait-detectmate/DetectMatePerformance. When parsing large numbers of log lines in non-stream (batch) mode, it is recommended to use the performance-oriented implementation. | ||
|
|
||
| | | Schema | Description | | ||
| |------------|----------------------------|--------------------| | ||
| | **Input** | [LogSchema](../schemas.md) | Unstructured log | | ||
| | **Output** | [ParserSchema](../schemas.md) | Structured log | | ||
|
|
||
| ## Configuration | ||
|
|
||
| Drain parser parameters: | ||
|
|
||
| - `method_type` (string): identifier for the parser type (e.g., `"tree_matcher"`). | ||
| - `depth` (int): number of token/word levels. | ||
| - `max_childs` (int): maximum number of children allowed in the given layer. | ||
| - `sim_thres` (float): threshold used for similarity. | ||
| - `reset_in_post_train` (bool): if enabled, clears logs from the training buffer once templates are created; otherwise, it keeps them for the next training cycle. | ||
| - `auto_config` (bool): indicates whether to run an optional auto-configuration step (not mandatory). | ||
|
|
||
| Example YAML fragment: | ||
| ```yaml | ||
| parsers: | ||
| DrainParser: | ||
| method_type: drain_parser | ||
| auto_config: False | ||
| params: | ||
| depth: 2 | ||
| ``` | ||
|
|
||
| ## Usage example | ||
|
|
||
| Simple usage (Reset = False): | ||
|
|
||
| ```python | ||
| --8<-- "docs/examples/parsers/drain_parser.py:example_1" | ||
| ``` | ||
|
|
||
| Simple usage (Reset = True): | ||
|
|
||
| ```python | ||
| --8<-- "docs/examples/parsers/drain_parser.py:example_2" | ||
| ``` | ||
|
|
||
| Go back to [Index](../index.md) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| from detectmatelibrary.common.parser import CoreParser, CoreParserConfig | ||
| from detectmatelibrary import schemas | ||
|
|
||
| from detectmateperformance.match_tree import TreeMatcher | ||
| from detectmateperformance.drain import Drain | ||
|
|
||
| from detectmatelibrary.utils.finetune import Combinations | ||
|
|
||
| from typing import Any | ||
|
|
||
|
|
||
| class DrainConfig(CoreParserConfig): | ||
| method_type: str = "drain_parser" | ||
|
|
||
| depth: int = 2 | ||
| max_childs: int = 10 | ||
| sim_thres: float = 0.2 | ||
|
|
||
| reset_in_post_train: bool = False | ||
|
|
||
| Finetune: list[list[str | list[Any]]] = [ | ||
| ["depth", [1, 2, 3, 4]], | ||
| ["max_childs", [10, 40]], | ||
| ["sim_thres", [0.2, 0.4, 0.6, 0.8]] | ||
| ] | ||
|
|
||
|
|
||
| def _init_drain(config: DrainConfig) -> Drain: | ||
| return Drain( | ||
| depth=config.depth, max_child=config.max_childs, sim=config.sim_thres, | ||
| ) | ||
|
|
||
|
|
||
| def _found_ratio(logs: list[str], tree_matcher: TreeMatcher) -> float: | ||
| results = tree_matcher.match_batch(logs).get_all_templates() | ||
|
|
||
| score = 0.0 | ||
| for template in results: | ||
| if "template not found" == template: | ||
| score += 1. | ||
|
|
||
| return score / len(results) | ||
|
|
||
|
|
||
| def _get_best_config(logs: list[str], config: DrainConfig) -> DrainConfig: | ||
|
|
||
| found_ratio: list[float] = [] | ||
| length: list[int] = [] | ||
|
|
||
| for config in (comb := Combinations(config))(): # type: ignore | ||
| drain = _init_drain(config) | ||
| for input_ in logs: | ||
| drain.add(input_) | ||
| tree_matcher = drain.generate() | ||
|
|
||
| found_ratio.append(_found_ratio(logs, tree_matcher)) | ||
| length.append(len(tree_matcher)) | ||
|
|
||
| n = max(length) | ||
| for le, sc in zip(length, found_ratio): | ||
| comb.add_value((float(le) / n) + sc) | ||
|
|
||
| new_config: DrainConfig = comb.get_best() # type: ignore | ||
| return new_config | ||
|
|
||
|
|
||
| class DrainParser(CoreParser): | ||
| def __init__( | ||
| self, | ||
| name: str = "DrainParser", | ||
| config: DrainConfig | dict[str, Any] = DrainConfig() | ||
| ) -> None: | ||
|
|
||
| if isinstance(config, dict): | ||
| config = DrainConfig.from_dict(config, name) | ||
| super().__init__(name=name, config=config) | ||
|
|
||
| self.config: DrainConfig | ||
| self.drain_gen = _init_drain(config=config) | ||
| self.tree_match: TreeMatcher | None = None | ||
|
|
||
| self.config_buffer: list[str] = [] | ||
|
|
||
| def configure(self, input_: schemas.LogSchema) -> None: # type: ignore | ||
| self.config_buffer.append(input_["log"]) | ||
|
|
||
| def set_configuration(self) -> None: | ||
| self.config = _get_best_config(self.config_buffer, config=self.config) | ||
| self.config_buffer = [] | ||
|
|
||
| def train(self, input_: schemas.LogSchema) -> None: # type: ignore | ||
| self.drain_gen.add(input_["log"]) | ||
|
|
||
| def post_train(self) -> None: | ||
| self.tree_match = self.drain_gen.generate() | ||
| if self.config.reset_in_post_train: | ||
| self.drain_gen.reset() | ||
|
|
||
| def parse( | ||
| self, | ||
| input_: schemas.LogSchema, | ||
| output_: schemas.ParserSchema | ||
| ) -> None: | ||
|
|
||
| if self.tree_match is None: | ||
| output_["EventID"] = -1 | ||
| output_["template"] = "templates not yet generated" | ||
| else: | ||
| parsed = self.tree_match.match_log(input_["log"], get_var=True)[0] | ||
|
|
||
| output_["EventID"] = parsed["EventID"] | ||
| output_["variables"].extend(parsed["ParamList"]) | ||
| output_["template"] = parsed["Template"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| """Most of the functionality is test it in DetectMatePerformance.""" | ||
| from detectmatelibrary.parsers.drain import DrainParser, _found_ratio | ||
|
|
||
| from detectmateperformance.match_tree import TreeMatcher | ||
| from detectmateperformance.types_ import LogTemplates | ||
|
|
||
| from detectmatelibrary import schemas | ||
|
|
||
|
|
||
| class TestDrainParser: | ||
| def test_train_process(self): | ||
| config_dict = { | ||
| "parsers": { | ||
| "DrainParser": { | ||
| "method_type": "drain_parser", | ||
| "depth": 2, | ||
| "max_childs": 10, | ||
| "sim_thres": 0.2, | ||
| "data_use_training": 2, | ||
| } | ||
| } | ||
| } | ||
| parser = DrainParser(config=config_dict) | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, general kenobi!"})) | ||
| assert parsed["EventID"] == -1 | ||
| assert parsed["template"] == "templates not yet generated" | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, captain kenobi!"})) | ||
| assert parsed["EventID"] == -1 | ||
| assert parsed["template"] == "templates not yet generated" | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, sargent kenobi!"})) | ||
| assert parsed["EventID"] == 0 | ||
| assert parsed["template"] == "hello there <*> kenobi" | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, general R2D2!"})) | ||
| assert parsed["EventID"] == -1 | ||
| assert parsed["template"] == "template not found" | ||
|
|
||
| def test_reset_after_train(self): | ||
| config_dict = { | ||
| "parsers": { | ||
| "DrainParser": { | ||
| "method_type": "drain_parser", | ||
| "depth": 2, | ||
| "max_childs": 10, | ||
| "sim_thres": 0.2, | ||
| "data_use_training": 2, | ||
| "reset_in_post_train": True, | ||
| } | ||
| } | ||
| } | ||
| parser = DrainParser(config=config_dict) | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, general kenobi!"})) | ||
| assert parsed["EventID"] == -1 | ||
| assert parsed["template"] == "templates not yet generated" | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, captain kenobi!"})) | ||
| assert parsed["EventID"] == -1 | ||
| assert parsed["template"] == "templates not yet generated" | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, sargent kenobi!"})) | ||
| assert parsed["EventID"] == 0 | ||
| assert parsed["template"] == "hello there <*> kenobi" | ||
|
|
||
| parser.update_state("keep_training") | ||
| parsed = parser.process(schemas.LogSchema({"log": "bella ciao bella ciao"})) | ||
|
ipmach marked this conversation as resolved.
Dismissed
|
||
| parser.update_state("stop_training") | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, sargent kenobi!"})) | ||
| assert parsed["EventID"] == -1 | ||
| assert parsed["template"] == "template not found" | ||
|
|
||
| def test_not_reset_train(self): | ||
| config_dict = { | ||
| "parsers": { | ||
| "DrainParser": { | ||
| "method_type": "drain_parser", | ||
| "depth": 2, | ||
| "max_childs": 10, | ||
| "sim_thres": 0.2, | ||
| "data_use_training": 2, | ||
| "reset_in_post_train": False, | ||
| } | ||
| } | ||
| } | ||
| parser = DrainParser(config=config_dict) | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, general kenobi!"})) | ||
| assert parsed["EventID"] == -1 | ||
| assert parsed["template"] == "templates not yet generated" | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, captain kenobi!"})) | ||
| assert parsed["EventID"] == -1 | ||
| assert parsed["template"] == "templates not yet generated" | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, sargent kenobi!"})) | ||
| assert parsed["EventID"] == 0 | ||
| assert parsed["template"] == "hello there <*> kenobi" | ||
|
|
||
| parser.update_state("keep_training") | ||
| parsed = parser.process(schemas.LogSchema({"log": "bella ciao bella ciao"})) | ||
|
ipmach marked this conversation as resolved.
Dismissed
|
||
| parser.update_state("stop_training") | ||
|
|
||
| parsed = parser.process(schemas.LogSchema({"log": "hello there, sargent kenobi!"})) | ||
| assert parsed["template"] == "hello there <*> kenobi" | ||
|
|
||
| def test_not_ration_found(self): | ||
| tree_matcher = TreeMatcher(LogTemplates(["hello there <*> kenobi"])) | ||
|
|
||
| logs = ["hello there general kenobi", "akuna matata"] | ||
| assert 0.5 == _found_ratio(logs, tree_matcher) | ||
|
|
||
| logs = ["hello there general kenobi"] | ||
| assert 0. == _found_ratio(logs, tree_matcher) | ||
|
|
||
| def test_no_auto_config_but_no_initialization(self): | ||
| config_dict = { | ||
| "parsers": { | ||
| "DrainParser": { | ||
| "method_type": "drain_parser", | ||
| "depth": 2, | ||
| "max_childs": 10, | ||
| "sim_thres": 0.2, | ||
| "auto_config": True, | ||
| "data_use_configure": 2, | ||
| "data_use_training": 2, | ||
| } | ||
| } | ||
| } | ||
| parser = DrainParser(config=config_dict) | ||
| parser.process(schemas.LogSchema({"log": "hello there, general kenobi!"})) | ||
| parser.process(schemas.LogSchema({"log": "hello there, captain kenobi!"})) | ||
| parser.process(schemas.LogSchema({"log": "hello there, captain kenobi!"})) | ||
|
|
||
| assert parser.config.depth == 1 | ||
| assert parser.config.max_childs == 10 | ||
| assert parser.config.sim_thres == 0.2 | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.