2022-07-15 17:01:30 +00:00
|
|
|
import json
|
2024-11-07 10:18:20 +00:00
|
|
|
from datetime import datetime
|
2022-08-19 02:43:38 +00:00
|
|
|
from xml.etree import ElementTree as ET
|
2022-07-15 17:01:30 +00:00
|
|
|
|
2024-09-27 10:19:39 +00:00
|
|
|
import pytest
|
|
|
|
|
2024-11-07 12:05:17 +00:00
|
|
|
from helpers.cluster import ClickHouseCluster
|
2024-11-07 12:23:18 +00:00
|
|
|
|
2022-07-15 17:01:30 +00:00
|
|
|
cluster = ClickHouseCluster(__file__)
|
2022-08-19 03:05:20 +00:00
|
|
|
node_all_keys = cluster.add_instance(
|
|
|
|
"node_all_keys", main_configs=["configs/config_all_keys_json.xml"]
|
|
|
|
)
|
|
|
|
node_some_keys = cluster.add_instance(
|
|
|
|
"node_some_keys", main_configs=["configs/config_some_keys_json.xml"]
|
|
|
|
)
|
|
|
|
node_no_keys = cluster.add_instance(
|
|
|
|
"node_no_keys", main_configs=["configs/config_no_keys_json.xml"]
|
|
|
|
)
|
2022-07-15 17:01:30 +00:00
|
|
|
|
2022-08-02 03:48:57 +00:00
|
|
|
|
2022-07-15 17:01:30 +00:00
|
|
|
@pytest.fixture(scope="module")
|
|
|
|
def start_cluster():
|
|
|
|
try:
|
|
|
|
cluster.start()
|
|
|
|
yield cluster
|
|
|
|
finally:
|
|
|
|
cluster.shutdown()
|
|
|
|
|
2022-08-02 03:48:57 +00:00
|
|
|
|
2022-07-15 17:01:30 +00:00
|
|
|
def is_json(log_json):
|
|
|
|
try:
|
|
|
|
json.loads(log_json)
|
|
|
|
except ValueError as e:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2022-08-19 03:05:20 +00:00
|
|
|
|
2024-01-18 13:40:23 +00:00
|
|
|
def validate_log_level(config, logs):
|
|
|
|
root = ET.fromstring(config)
|
|
|
|
key = root.findtext(".//names/level") or "level"
|
|
|
|
|
|
|
|
valid_level_values = {
|
2024-01-18 15:42:10 +00:00
|
|
|
"Fatal",
|
|
|
|
"Critical",
|
|
|
|
"Error",
|
|
|
|
"Warning",
|
|
|
|
"Notice",
|
|
|
|
"Information",
|
|
|
|
"Debug",
|
|
|
|
"Trace",
|
2024-01-18 13:40:23 +00:00
|
|
|
"Test",
|
|
|
|
}
|
|
|
|
|
|
|
|
length = min(10, len(logs))
|
|
|
|
for i in range(0, length):
|
|
|
|
json_log = json.loads(logs[i])
|
|
|
|
if json_log[key] not in valid_level_values:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2024-11-07 10:18:20 +00:00
|
|
|
def is_valid_utc_datetime(datetime_str):
|
|
|
|
try:
|
|
|
|
datetime_obj = datetime.strptime(datetime_str, "%Y-%m-%dT%H:%M:%S.%fZ")
|
|
|
|
return datetime_obj.tzinfo is None
|
|
|
|
except ValueError:
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
2022-08-19 02:43:38 +00:00
|
|
|
def validate_log_config_relation(config, logs, config_type):
|
|
|
|
root = ET.fromstring(config)
|
|
|
|
keys_in_config = set()
|
2022-07-15 17:01:30 +00:00
|
|
|
|
2022-08-19 02:43:38 +00:00
|
|
|
if config_type == "config_no_keys":
|
|
|
|
keys_in_config.add("date_time")
|
2024-11-07 10:18:20 +00:00
|
|
|
keys_in_config.add("date_time_utc")
|
2022-08-19 02:43:38 +00:00
|
|
|
keys_in_config.add("thread_name")
|
|
|
|
keys_in_config.add("thread_id")
|
|
|
|
keys_in_config.add("level")
|
|
|
|
keys_in_config.add("query_id")
|
|
|
|
keys_in_config.add("logger_name")
|
|
|
|
keys_in_config.add("message")
|
|
|
|
keys_in_config.add("source_file")
|
|
|
|
keys_in_config.add("source_line")
|
|
|
|
else:
|
2022-08-19 03:05:20 +00:00
|
|
|
for child in root.findall(".//names/*"):
|
2022-08-19 02:43:38 +00:00
|
|
|
keys_in_config.add(child.text)
|
2022-07-15 17:01:30 +00:00
|
|
|
|
2022-08-19 02:43:38 +00:00
|
|
|
try:
|
|
|
|
length = min(10, len(logs))
|
|
|
|
for i in range(0, length):
|
|
|
|
json_log = json.loads(logs[i])
|
|
|
|
keys_in_log = set()
|
|
|
|
for log_key in json_log.keys():
|
|
|
|
keys_in_log.add(log_key)
|
|
|
|
if log_key not in keys_in_config:
|
|
|
|
return False
|
2024-11-07 10:18:20 +00:00
|
|
|
|
|
|
|
# Validate the UTC datetime format in "date_time_utc" if it exists
|
|
|
|
if "date_time_utc" in json_log and not is_valid_utc_datetime(
|
|
|
|
json_log["date_time_utc"]
|
|
|
|
):
|
|
|
|
return False
|
2022-08-19 02:43:38 +00:00
|
|
|
except ValueError as e:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2022-08-19 03:05:20 +00:00
|
|
|
|
2022-08-19 02:43:38 +00:00
|
|
|
def validate_logs(logs):
|
2022-08-02 03:21:48 +00:00
|
|
|
length = min(10, len(logs))
|
2022-08-19 02:43:38 +00:00
|
|
|
result = True
|
2022-08-02 03:21:48 +00:00
|
|
|
for i in range(0, length):
|
2022-08-19 02:43:38 +00:00
|
|
|
result = result and is_json(logs[i])
|
|
|
|
return result
|
|
|
|
|
2022-08-19 03:05:20 +00:00
|
|
|
|
2024-11-10 12:10:25 +00:00
|
|
|
def validate_everything(config, node, config_type):
|
2022-08-19 02:43:38 +00:00
|
|
|
node.query("SELECT 1")
|
|
|
|
logs = node.grep_in_log("").split("\n")
|
2024-01-18 15:42:10 +00:00
|
|
|
return (
|
|
|
|
validate_logs(logs)
|
|
|
|
and validate_log_config_relation(config, logs, config_type)
|
|
|
|
and validate_log_level(config, logs)
|
|
|
|
)
|
2022-08-19 02:43:38 +00:00
|
|
|
|
2022-08-19 03:34:24 +00:00
|
|
|
|
|
|
|
def test_structured_logging_json_format(start_cluster):
|
2022-08-19 03:05:20 +00:00
|
|
|
config_all_keys = node_all_keys.exec_in_container(
|
|
|
|
["cat", "/etc/clickhouse-server/config.d/config_all_keys_json.xml"]
|
|
|
|
)
|
|
|
|
config_some_keys = node_some_keys.exec_in_container(
|
|
|
|
["cat", "/etc/clickhouse-server/config.d/config_some_keys_json.xml"]
|
|
|
|
)
|
|
|
|
config_no_keys = node_no_keys.exec_in_container(
|
|
|
|
["cat", "/etc/clickhouse-server/config.d/config_no_keys_json.xml"]
|
|
|
|
)
|
|
|
|
|
2024-11-10 12:10:25 +00:00
|
|
|
assert validate_everything(config_all_keys, node_all_keys, "config_all_keys") == True
|
2022-08-29 02:24:25 +00:00
|
|
|
assert (
|
2024-11-10 12:10:25 +00:00
|
|
|
validate_everything(config_some_keys, node_some_keys, "config_some_keys") == True
|
2022-08-29 02:24:25 +00:00
|
|
|
)
|
2024-11-10 12:10:25 +00:00
|
|
|
assert validate_everything(config_no_keys, node_no_keys, "config_no_keys") == True
|