diff --git a/pyproject.toml b/pyproject.toml index 92307a7c..76add752 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ [tool.poetry] name = "solnlib" -version = "5.1.2" +version = "5.2.0-beta.1" description = "The Splunk Software Development Kit for Splunk Solutions" authors = ["Splunk "] license = "Apache-2.0" diff --git a/solnlib/__init__.py b/solnlib/__init__.py index 50ae5eb9..425e6531 100644 --- a/solnlib/__init__.py +++ b/solnlib/__init__.py @@ -56,4 +56,4 @@ "utils", ] -__version__ = "5.1.2" +__version__ = "5.2.0-beta.1" diff --git a/solnlib/log.py b/solnlib/log.py index 3f1958e1..7c5cf11a 100644 --- a/solnlib/log.py +++ b/solnlib/log.py @@ -79,7 +79,7 @@ class Logs(metaclass=Singleton): _default_directory = None _default_namespace = None _default_log_format = ( - "%(asctime)s %(levelname)s pid=%(process)d tid=%(threadName)s " + "%(asctime)s log_level=%(levelname)s pid=%(process)d tid=%(threadName)s " "file=%(filename)s:%(funcName)s:%(lineno)d | %(message)s" ) _default_log_level = logging.INFO @@ -285,6 +285,7 @@ def events_ingested( index: str, account: str = None, host: str = None, + license_usage_source: str = None, ): """Specific function to log the basic information of events ingested for the monitoring dashboard. @@ -296,6 +297,7 @@ def events_ingested( sourcetype: Source type used to write event. n_events: Number of ingested events. index: Index used to write event. + license_usage_source: source used to match data with license_usage.log. account: Account used to write event. (optional) host: Host used to write event. (optional) """ @@ -310,7 +312,9 @@ def events_ingested( result = { "action": "events_ingested", - "modular_input_name": modular_input_name, + "modular_input_name": license_usage_source + if license_usage_source + else modular_input_name, "sourcetype_ingested": sourcetype, "n_events": n_events, "event_input": input_name, diff --git a/tests/integration/test_bulletin_rest_client.py b/tests/integration/test_bulletin_rest_client.py index 21e344b7..283e9c8b 100644 --- a/tests/integration/test_bulletin_rest_client.py +++ b/tests/integration/test_bulletin_rest_client.py @@ -56,9 +56,12 @@ def test_bulletin_rest_api(): bulletin_client_1 = _build_bulletin_manager("msg_name_1", session_key) bulletin_client_2 = _build_bulletin_manager("msg_name_2", session_key) + # clear bulletin before tests + _clear_bulletin() + bulletin_client_1.create_message( "new message to bulletin", - capabilities=["apps_restore", "delete_messages"], + capabilities=["apps_restore", "edit_roles"], roles=["admin"], ) @@ -106,3 +109,13 @@ def test_bulletin_rest_api(): get_all_msg = bulletin_client_1.get_all_messages() assert len(get_all_msg["entry"]) == 0 + + +def _clear_bulletin(): + session_key = context.get_session_key() + bulletin_client = _build_bulletin_manager("", session_key) + + msg_to_del = [el["name"] for el in bulletin_client.get_all_messages()["entry"]] + for msg in msg_to_del: + endpoint = f"{bulletin_client.MESSAGES_ENDPOINT}/{msg}" + bulletin_client._rest_client.delete(endpoint) diff --git a/tests/unit/test_log.py b/tests/unit/test_log.py index aa0298d4..f3688e74 100644 --- a/tests/unit/test_log.py +++ b/tests/unit/test_log.py @@ -18,10 +18,13 @@ import json import multiprocessing import os +import re import shutil import threading import traceback import time +from textwrap import dedent + import pytest from unittest import mock @@ -240,6 +243,42 @@ def test_events_ingested_invalid_input(): assert exp_msg == str(excinfo.value) +def test_events_ingested_custom_license_usage(): + with mock.patch("logging.Logger") as mock_logger: + log.events_ingested( + mock_logger, + "input_type://input_name", + "sourcetype", + 5, + "default", + license_usage_source="custom:license:source", + ) + + mock_logger.log.assert_called_once_with( + logging.INFO, + "action=events_ingested modular_input_name=custom:license:source sourcetype_ingested=sourcetype " + "n_events=5 event_input=input_name event_index=default", + ) + + with mock.patch("logging.Logger") as mock_logger: + log.events_ingested( + mock_logger, + "demo://modular_input_name", + "sourcetype", + 5, + "default", + host="abcd", + account="test_acc", + license_usage_source="custom:license:source:123", + ) + + mock_logger.log.assert_called_once_with( + logging.INFO, + "action=events_ingested modular_input_name=custom:license:source:123 sourcetype_ingested=sourcetype n_" + "events=5 event_input=modular_input_name event_index=default event_account=test_acc event_host=abcd", + ) + + def test_log_exceptions_full_msg(): start_msg = "some msg before exception" with mock.patch("logging.Logger") as mock_logger: @@ -300,3 +339,33 @@ class AddonComplexError(Exception): mock_logger.log.assert_called_with( logging.ERROR, f"exc_l={result} \n{traceback.format_exc()}\n" ) + + +def test_log_format(monkeypatch, tmp_path): + log_file = tmp_path / "logging_levels.log" + + monkeypatch.setattr(log.Logs, "_get_log_file", lambda _, name: str(log_file)) + + logger = log.Logs().get_logger("logging_levels") + + logger.warning("log 2") + logger.error("log 3") + + log_content = transform_log(log_file.read_text()) + + assert ( + log_content + == dedent( + """ + 2024-03-23 10:15:20,555 log_level=WARNING pid=1234 tid=MainThread file=test_file.py:test_func:123 | log 2 + 2024-03-23 10:15:20,555 log_level=ERROR pid=1234 tid=MainThread file=test_file.py:test_func:123 | log 3 + """, + ).lstrip() + ) + + +def transform_log(log: str) -> str: + log = re.sub(r"pid=\d+", "pid=1234", log) + log = re.sub(r"file=[^ ]+", "file=test_file.py:test_func:123", log) + log = re.sub(r"\d{4}-\d\d-\d\d \d\d[^ ]+", "2024-03-23 10:15:20,555", log) + return log