Skip to content
1 change: 1 addition & 0 deletions docs/alert_aggregators/basic_concatenation.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ alert_aggregators:
BasicConcatAggregator:
method_type: "basic_concat_aggregator"
buffer_size: 3
deduplicate_values: False
auto_config: False
```

Expand Down
1 change: 1 addition & 0 deletions docs/examples/alert_aggregators/basic_concat.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"BasicConcatAggregator": {
"method_type": "basic_concat_aggregator",
"buffer_size": 3,
"deduplicate_values": False,
"auto_config": False,
}
}
Expand Down
27 changes: 27 additions & 0 deletions src/detectmatelibrary/_testutils/dummy_alert_aggregator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from detectmatelibrary.common.alert_aggregator import (CoreAlertAggregatorConfig, CoreAlertAggregator)
from detectmatelibrary.schemas import DetectorSchema, AggregateSchema
from typing import Any, Optional


class DummyAlertAggregatorConfig(CoreAlertAggregatorConfig):
method_type: str = "dummy_alert_aggregator"
buffer_size: int = 3


class DummyAlertAggregator(CoreAlertAggregator):
def __init__(
self,
name: str = "DummyAlertAggregator",
config: Optional[DummyAlertAggregatorConfig | dict[str, Any]] = DummyAlertAggregatorConfig(),
) -> None:
if isinstance(config, dict):
config = DummyAlertAggregatorConfig.from_dict(config, name)
buffer_size: int = config.buffer_size # type: ignore
super().__init__(name=name, buffer_size=buffer_size, config=config)

def aggregate_alerts(
self, input_: list[DetectorSchema] | DetectorSchema, output_: AggregateSchema
) -> bool:
output_["description"] = "Dummy alert aggregation"
output_["alertsObtain"]["type"] = "Anomalies aggregated by DummyAlertAggregator"
return True
6 changes: 4 additions & 2 deletions src/detectmatelibrary/_testutils/dummy_detector.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from detectmatelibrary.common.detector import CoreDetector, CoreDetectorConfig
from detectmatelibrary.utils.data_buffer import BufferMode
from detectmatelibrary import schemas
from typing import List, Any
from typing import List, Any, Optional


class DummyDetectorConfig(CoreDetectorConfig):
Expand All @@ -15,12 +15,14 @@ class DummyDetector(CoreDetector):
def __init__(
self,
name: str = "DummyDetector",
buffer_mode: BufferMode = BufferMode.NO_BUF,
buffer_size: Optional[int] = None,
config: DummyDetectorConfig | dict[str, Any] = DummyDetectorConfig()
) -> None:

if isinstance(config, dict):
config = DummyDetectorConfig.from_dict(config, name)
super().__init__(name=name, buffer_mode=BufferMode.NO_BUF, config=config)
super().__init__(name=name, buffer_mode=buffer_mode, buffer_size=buffer_size, config=config)
self._call_count = 0

def detect(
Expand Down
18 changes: 10 additions & 8 deletions src/detectmatelibrary/alert_aggregation/basic_concat.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
from detectmatelibrary.common.alert_aggregator import (
CoreAlertAggregatorConfig, CoreAlertAggregator
)

from detectmatelibrary.common.alert_aggregator import (CoreAlertAggregatorConfig, CoreAlertAggregator)
from detectmatelibrary.schemas import DetectorSchema, AggregateSchema

from typing import Any, Optional


class BasicConcatAggregationConfig(CoreAlertAggregatorConfig):
method_type: str = "basic_concat_aggregator"
buffer_size: int = 3
deduplicate_values: bool = False


class BasicConcatAggregation(CoreAlertAggregator):
Expand All @@ -20,13 +17,18 @@ def __init__(
) -> None:
if isinstance(config, dict):
config = BasicConcatAggregationConfig.from_dict(config, name)

buffer_size: int = config.buffer_size # type: ignore
self.deduplicate_values: bool = config.deduplicate_values # type: ignore
super().__init__(name=name, buffer_size=buffer_size, config=config)

def aggregate_alerts(
self, input_: list[DetectorSchema], output_: AggregateSchema # type: ignore
self, input_: list[DetectorSchema] | DetectorSchema, output_: AggregateSchema
) -> bool:

output_["description"] = "Basic aggregation by alert concatenation"
if self.deduplicate_values:
output_["detectorIDs"] = list(dict.fromkeys(output_["detectorIDs"]))
output_["detectorTypes"] = list(dict.fromkeys(output_["detectorTypes"]))
output_["alertIDs"] = list(dict.fromkeys(output_["alertIDs"]))
output_["logIDs"] = list(dict.fromkeys(output_["logIDs"]))
output_["extractedTimestamps"] = list(dict.fromkeys(output_["extractedTimestamps"]))
return True
16 changes: 9 additions & 7 deletions src/detectmatelibrary/tools/workspace/create_workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
META_FILES = ["LICENSE.md", ".gitignore", ".pre-commit-config.yaml"]
DATA_FILES = {
"parser": TEMPLATE_DIR / "data/logs.json",
"detector": TEMPLATE_DIR / "data/parsed_log.json"
"detector": TEMPLATE_DIR / "data/parsed_log.json",
"alert_aggregator": TEMPLATE_DIR / "data/alerts.json"
}


Expand All @@ -38,12 +39,14 @@ def create_tests(type_: str, name: str, workspace_root: Path, pkg_name: str) ->

- Reads template tests from src/tools/workspace/templates/test_templates/
- Rewrites the import to point to <pkg_name>.<name>
- Renames CustomParser/CustomDetector to the camelized class name
- Renames CustomParser/CustomDetector/CustomAlertAggregator to the camelized class name
"""

tests_dir = workspace_root / "tests"
tests_dir.mkdir(parents=True, exist_ok=True)
template_file = TEMPLATE_DIR / "test_templates" / f"test_Custom{type_.capitalize()}.py"
# base names in the template (CustomParser/CustomDetector/CustomAlertAggregator)
base_class = f"Custom{"".join(part.capitalize() for part in type_.split("_"))}"
template_file = TEMPLATE_DIR / "test_templates" / f"test_{base_class}.py"
test_file = tests_dir / f"test_{name}.py"

if not template_file.exists():
Expand All @@ -52,7 +55,6 @@ def create_tests(type_: str, name: str, workspace_root: Path, pkg_name: str) ->
return

template_content = template_file.read_text()
base_class = f"Custom{type_.capitalize()}" # base names in the template (CustomParser/CustomDetector)
# The exact import line in the template, e.g:
# from ..CustomParser import CustomParser, CustomParserConfig
# from ..CustomDetector import CustomDetector, CustomDetectorConfig
Expand Down Expand Up @@ -98,7 +100,7 @@ def create_workspace(type_: str, name: str, target_dir: Path) -> None:
pkg_dir.mkdir(parents=True, exist_ok=False)

# Template selection
template_file = TEMPLATE_DIR / f"Custom{type_.capitalize()}.py"
template_file = TEMPLATE_DIR / f"Custom{"".join(part.capitalize() for part in type_.split("_"))}.py"
module_name = normalize_package_name(name)
target_code_file = pkg_dir / f"{module_name}.py"

Expand All @@ -109,7 +111,7 @@ def create_workspace(type_: str, name: str, target_dir: Path) -> None:
template_content = template_file.read_text()

# Replace default class name inside template
original_class = f"Custom{type_.capitalize()}"
original_class = f"Custom{"".join(part.capitalize() for part in type_.split("_"))}"
new_class = camelize(name)
template_content = template_content.replace(original_class, new_class)
template_content = template_content.replace(f"custom_{type_}", f"{name}_{type_}")
Expand Down Expand Up @@ -164,7 +166,7 @@ def main() -> None:
create_cmd.add_argument(
"--type",
required=True,
choices=["parser", "detector"],
choices=["parser", "detector", "alert_aggregator"],
help="Type of component to generate",
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from detectmatelibrary.common.alert_aggregator import (CoreAlertAggregatorConfig, CoreAlertAggregator)
from detectmatelibrary.schemas import DetectorSchema, AggregateSchema
from typing import Any, Optional
from detectmatelibrary.helper.from_to import From


class CustomAlertAggregatorConfig(CoreAlertAggregatorConfig):
method_type: str = "custom_alert_aggregator"
buffer_size: int = 3


class CustomAlertAggregator(CoreAlertAggregator):
def __init__(
self,
name: str = "CustomAlertAggregator",
config: Optional[CustomAlertAggregatorConfig | dict[str, Any]] = CustomAlertAggregatorConfig(),
) -> None:
if isinstance(config, dict):
config = CustomAlertAggregatorConfig.from_dict(config, name)
buffer_size: int = config.buffer_size # type: ignore
super().__init__(name=name, buffer_size=buffer_size, config=config)

def aggregate_alerts(
self, input_: list[DetectorSchema] | DetectorSchema, output_: AggregateSchema
) -> bool:
output_["description"] = "Custom alert aggregation"
output_["alertsObtain"]["type"] = "Anomalies aggregated by CustomAlertAggregator"
return True


if __name__ == "__main__":
print(aggregator := CustomAlertAggregator())
print("Running with data...")
for alerts in From.json(aggregator, "data.json"):
print(alerts)
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from typing import Any, List

from detectmatelibrary.common.detector import CoreDetector, CoreDetectorConfig
from detectmatelibrary.utils.data_buffer import BufferMode
from detectmatelibrary.helper.from_to import From
Expand All @@ -24,7 +23,6 @@ def __init__(
name: str = "CustomDetector",
config: CustomDetectorConfig | dict[str, Any] = CustomDetectorConfig(),
) -> None:

# Allow passing either a config instance or a plain dict
if isinstance(config, dict):
config = CustomDetectorConfig.from_dict(config, name)
Expand All @@ -43,24 +41,19 @@ def detect(
:param output_: DetectorSchema instance to be mutated in-place
:return: Detection result (True/False)
"""

output_["description"] = "Dummy detection process" # Description of the detection

# Alternating pattern: True, False, True, False, etc
self._call_count += 1
pattern = [True, False]
result = pattern[self._call_count % len(pattern)]
if result:
output_["score"] = 1.0 # Score of the detector
output_["alertsObtain"]["type"] = "Anomaly detected by CustomDetector" # Additional info

return result


if __name__ == "__main__":

print(detector := CustomDetector())

print("Running with data...")
for alerts in From.json(detector, "data.json"):
print(alerts)
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from typing import Any

from detectmatelibrary.common.parser import CoreParser, CoreParserConfig
from detectmatelibrary.helper.from_to import From
from detectmatelibrary import schemas
Expand All @@ -25,7 +24,6 @@ def __init__(
# Allow passing either a config instance or a plain dict
if isinstance(config, dict):
config = CustomParserConfig.from_dict(config, name)

super().__init__(name=name, config=config)

def parse(
Expand All @@ -39,17 +37,14 @@ def parse(
:param output_: Parser output schema instance to be mutated in-
place
"""

# Dummy implementation example (replace with real logic)
output_["EventID"] = 2 # Number of the log template
output_["variables"].extend(["dummy_variable"]) # Variables found in the log
output_["template"] = "This is a dummy template" # Log template


if __name__ == "__main__":

print(parser := CustomParser())

print("Running with data...")
for parsed_log in From.json(parser, "data.json"):
print(parsed_log)
Loading
Loading