From 13bf2a16a1f61cf68cde88d5928f7ceae5396a28 Mon Sep 17 00:00:00 2001
From: jinsaihang <jinsaihang@h-partners.com>
Date: Fri, 24 Jan 2025 16:24:43 +0800
Subject: [PATCH] adapt file

---
 config/xalarm.conf                    |   3 +
 src/python/syssentry/alarm.py         | 209 ++++++++++++++++++++++++++
 src/python/syssentry/callbacks.py     |  17 +++
 src/python/syssentry/global_values.py |   4 +
 src/python/syssentry/load_mods.py     |  16 ++
 src/python/syssentry/sentryctl        |  25 ++-
 src/python/syssentry/syssentry.py     |  14 +-
 src/python/syssentry/task_map.py      |   5 +-
 src/python/xalarm/register_xalarm.py  |   4 +-
 src/python/xalarm/xalarm_api.py       |   6 +-
 src/python/xalarm/xalarm_config.py    |  33 +++-
 src/python/xalarm/xalarm_daemon.py    |   7 +-
 src/python/xalarm/xalarm_transfer.py  |   8 +-
 13 files changed, 332 insertions(+), 19 deletions(-)
 create mode 100644 src/python/syssentry/alarm.py

diff --git a/config/xalarm.conf b/config/xalarm.conf
index 14c6d39..323d2dd 100644
--- a/config/xalarm.conf
+++ b/config/xalarm.conf
@@ -1,2 +1,5 @@
 [filter]
 id_mask = 1001-1128
+
+[log]
+level=info
diff --git a/src/python/syssentry/alarm.py b/src/python/syssentry/alarm.py
new file mode 100644
index 0000000..e5cc313
--- /dev/null
+++ b/src/python/syssentry/alarm.py
@@ -0,0 +1,209 @@
+# coding: utf-8
+# Copyright (c) 2024 Huawei Technologies Co., Ltd.
+# sysSentry is licensed under the Mulan PSL v2.
+# You can use this software according to the terms and conditions of the Mulan PSL v2.
+# You may obtain a copy of Mulan PSL v2 at:
+#     http://license.coscl.org.cn/MulanPSL2
+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+# PURPOSE.
+# See the Mulan PSL v2 for more details.
+
+"""
+use for report alarm
+"""
+import threading
+from typing import Dict, List
+from datetime import datetime
+import time
+import logging
+import json
+import sys
+
+from xalarm.register_xalarm import xalarm_register,xalarm_getid,xalarm_getlevel,xalarm_gettype,xalarm_gettime,xalarm_getdesc
+from xalarm.xalarm_api import Xalarm
+
+from .global_values import InspectTask
+from .task_map import TasksMap
+
+# 告警ID映射字典，key为插件名，value为告警ID（类型为数字）
+task_alarm_id_dict: Dict[str, int] = {}
+
+# 告警老化时间字典，key为告警ID，value为老化时间（类型为数字，单位为秒）
+alarm_id_clear_time_dict: Dict[int, int] = {}
+
+# 告警事件列表，key为告警ID，value为告警ID对应的告警事件列表（类型为list）
+alarm_list_dict: Dict[int, List[Xalarm]] = {}
+# 告警事件列表锁
+alarm_list_lock = threading.Lock()
+
+id_filter = []
+id_base = 1001
+clientId = -1
+
+MILLISECONDS_UNIT_SECONDS = 1000
+MAX_NUM_OF_ALARM_ID = 128
+MIN_ALARM_ID = 1001
+MAX_ALARM_ID = (MIN_ALARM_ID + MAX_NUM_OF_ALARM_ID - 1)
+
+def update_alarm_list(alarm_info: Xalarm):
+    alarm_id = xalarm_getid(alarm_info)
+    if alarm_id < MIN_ALARM_ID or alarm_id > MAX_ALARM_ID:
+        logging.warning(f"Invalid alarm_id {alarm_id}")
+        return
+    timestamp = xalarm_gettime(alarm_info)
+    if not timestamp:
+        logging.error("Retrieve timestamp failed")
+        return
+    alarm_list_lock.acquire()
+    try:
+        # new alarm is inserted into list head
+        if alarm_id not in alarm_list_dict:
+            logging.warning(f"update_alarm_list: alarm_id {alarm_id} not found in alarm_list_dict")
+            return
+        alarm_list = alarm_list_dict[alarm_id]
+
+        alarm_list.insert(0, alarm_info)
+        # clear alarm_info older than clear time threshold
+        clear_index = -1
+        clear_time = alarm_id_clear_time_dict[alarm_id]
+        for i in range(len(alarm_list)):
+            if (timestamp - xalarm_gettime(alarm_list[i])) / MILLISECONDS_UNIT_SECONDS > clear_time:
+                clear_index = i
+                break
+        if clear_index >= 0:
+            alarm_list_dict[alarm_id] = alarm_list[:clear_index]
+    finally:
+        alarm_list_lock.release()
+
+def validate_alarm_id(alarm_id):
+    if alarm_id is None:
+        return False
+    try:
+        alarm_id = int(alarm_id)
+        if MIN_ALARM_ID <= alarm_id <= MAX_ALARM_ID:
+            return True
+        else:
+            return False
+    except ValueError:
+        return False
+
+def validate_alarm_clear_time(alarm_clear_time):
+    try:
+        alarm_clear_time = int(alarm_clear_time)
+        if alarm_clear_time > 0 and alarm_clear_time <= sys.maxsize:
+            return True
+        else:
+            return False
+    except ValueError:
+        return False
+
+def alarm_register():
+    logging.debug(f"alarm_register: enter")
+    # 初始化告警ID映射字典、告警老化时间字典
+    for task_type in TasksMap.tasks_dict:
+        for task_name in TasksMap.tasks_dict[task_type]:
+            task = TasksMap.tasks_dict[task_type][task_name]
+            if not validate_alarm_id(task.alarm_id):
+                logging.warning(f"Invalid alarm_id {task.alarm_id}: ignore {task_name} alarm")
+                continue
+            if not validate_alarm_clear_time(task.alarm_clear_time):
+                logging.warning(f"Invalid alarm_clear_time {task.alarm_clear_time}: ignore {task_name} alarm")
+                continue
+            task.alarm_id = int(task.alarm_id)
+            task.alarm_clear_time = int(task.alarm_clear_time)
+            alarm_id = task.alarm_id
+            alarm_clear_time = task.alarm_clear_time
+            
+            alarm_list_dict[alarm_id] = []
+            task_alarm_id_dict[task_name] = alarm_id
+            if alarm_id not in alarm_id_clear_time_dict:
+                alarm_id_clear_time_dict[alarm_id] = alarm_clear_time
+            else:
+                alarm_id_clear_time_dict[alarm_id] = max(alarm_clear_time, alarm_id_clear_time_dict[alarm_id])
+            logging.info(f"alarm_register: {task_name} is registered")
+    # 注册告警回调
+    id_filter = [True] * 128
+    clientId = xalarm_register(update_alarm_list, id_filter)
+    if clientId < 0:
+        logging.info(f'register xalarm: failed')
+        return clientId
+    logging.info('register xalarm: success')
+    return clientId
+
+def get_alarm_result(task_name: str, time_range: int, detailed: bool) -> List[Dict]:
+    alarm_list_lock.acquire()
+    try:
+        if task_name not in task_alarm_id_dict:
+            logging.debug("task_name does not exist")
+            return []
+        alarm_id = task_alarm_id_dict[task_name]
+        clear_time = alarm_id_clear_time_dict[alarm_id]
+        if alarm_id not in alarm_list_dict:
+            logging.debug("alarm_id does not exist")
+            return []
+        alarm_list = alarm_list_dict[alarm_id]
+        logging.debug(f"get_alarm_result: alarm_list of {alarm_id} has {len(alarm_list)} elements")
+        # clear alarm_info older than clear time threshold
+        stop_index = -1
+        timestamp = datetime.now().timestamp()
+        for i in range(len(alarm_list)):
+            logging.debug(f"timestamp, alarm_list[{i}].timestamp: {timestamp}, {xalarm_gettime(alarm_list[i])}")
+            if timestamp - (xalarm_gettime(alarm_list[i])) / MILLISECONDS_UNIT_SECONDS > time_range:
+                stop_index = i
+                break
+            if timestamp - (xalarm_gettime(alarm_list[i])) / MILLISECONDS_UNIT_SECONDS > clear_time:
+                stop_index = i
+                break
+        if stop_index >= 0:
+            alarm_list = alarm_list[:stop_index]
+        logging.debug(f"get_alarm_result: final alarm_list of {alarm_id} has {len(alarm_list)} elements")
+
+        def xalarm_to_dict(alarm_info: Xalarm) -> dict:
+            timestamp = alarm_info.timetamp.tv_sec + alarm_info.timetamp.tv_usec / 1000000
+            dt_object = datetime.fromtimestamp(int(timestamp))
+            return {
+                'alarm_id': xalarm_getid(alarm_info),
+                'alarm_type': xalarm_gettype(alarm_info),
+                'alarm_level': xalarm_getlevel(alarm_info),
+                'timestamp': dt_object.strftime("%Y-%m-%d %H:%M:%S"),
+                'msg1': xalarm_getdesc(alarm_info)
+            }
+
+        alarm_list = [xalarm_to_dict(alarm) for alarm in alarm_list]
+
+        # keep detail
+        for alarm in alarm_list:
+            alarm_info = alarm['msg1']
+            alarm_info = json.loads(alarm_info)
+            if not detailed:
+                if 'details' in alarm_info:
+                    alarm_info.pop('details', None)
+            alarm.pop('msg1', None)
+
+            # dump each {key,value} of details in one line
+            if 'details' in alarm_info and isinstance(alarm_info['details'], dict):
+                for key in alarm_info['details']:
+                    alarm_info['details'][key] = str(alarm_info['details'][key])
+
+            alarm['alarm_info'] = alarm_info
+        alarm_list = [alarm for alarm in alarm_list if 'alarm_source' in alarm['alarm_info'] and alarm['alarm_info']['alarm_source'] == task_name]
+
+        alarm_level_mapping = { 
+                1: 'MINOR_ALM',
+                2: 'MAJOR_ALM',
+                3: 'CRITICAL_ALM'
+                }   
+
+        alarm_type_mapping = { 
+                1: 'ALARM_TYPE_OCCUR',
+                2: 'ALARM_TYPE_RECOVER'
+                }   
+
+        for alarm in alarm_list:
+            alarm['alarm_level'] = alarm_level_mapping.get(alarm['alarm_level'], 'UNKNOWN_LEVEL')
+            alarm['alarm_type'] = alarm_type_mapping.get(alarm['alarm_type'], 'UNKNOWN_TYPE')
+        return alarm_list
+
+    finally:
+        alarm_list_lock.release()
diff --git a/src/python/syssentry/callbacks.py b/src/python/syssentry/callbacks.py
index d0d0719..d2dff24 100644
--- a/src/python/syssentry/callbacks.py
+++ b/src/python/syssentry/callbacks.py
@@ -18,6 +18,7 @@ import logging
 
 from .task_map import TasksMap, ONESHOT_TYPE, PERIOD_TYPE
 from .mod_status import EXITED_STATUS, RUNNING_STATUS, WAITING_STATUS, set_runtime_status
+from .alarm import get_alarm_result
 
 
 def task_get_status(mod_name):
@@ -41,6 +42,22 @@ def task_get_result(mod_name):
 
     return "success", task.get_result()
 
+def task_get_alarm(data):
+    """get alarm by mod name"""
+    task_name = data['task_name']
+    time_range = data['time_range']
+    try:
+        detailed = data['detailed']
+    except KeyError:
+        logging.debug("Key 'detailed' does not exist in the dictionary")
+        detailed = None
+    task = TasksMap.get_task_by_name(task_name)
+    if not task:
+        return "failed", f"cannot find task by name {task_name}"
+    if not task.load_enabled:
+        return "failed", f"mod {task_name} is not enabled"
+
+    return "success", get_alarm_result(task_name, time_range, detailed)
 
 def task_stop(mod_name):
     """stop by mod name"""
diff --git a/src/python/syssentry/global_values.py b/src/python/syssentry/global_values.py
index 483d544..b123b2d 100644
--- a/src/python/syssentry/global_values.py
+++ b/src/python/syssentry/global_values.py
@@ -27,6 +27,7 @@ CTL_SOCKET_PATH = "/var/run/sysSentry/control.sock"
 SYSSENTRY_CONF_PATH = "/etc/sysSentry"
 INSPECT_CONF_PATH = "/etc/sysSentry/inspect.conf"
 TASK_LOG_DIR = "/var/log/sysSentry"
+DEFAULT_ALARM_CLEAR_TIME = 15
 
 SENTRY_RUN_DIR_PERM = 0o750
 
@@ -76,6 +77,9 @@ class InspectTask:
         self.env_file = ""
         # start mode
         self.conflict = "up"
+        # alarm id
+        self.alarm_id = -1
+        self.alarm_clear_time = DEFAULT_ALARM_CLEAR_TIME
 
     def start(self):
         """
diff --git a/src/python/syssentry/load_mods.py b/src/python/syssentry/load_mods.py
index 5be5540..ac15380 100644
--- a/src/python/syssentry/load_mods.py
+++ b/src/python/syssentry/load_mods.py
@@ -24,6 +24,7 @@ from .task_map import TasksMap, ONESHOT_TYPE, PERIOD_TYPE
 from .cron_process import PeriodTask
 from .mod_status import set_task_status
 
+from xalarm.register_xalarm import MIN_ALARM_ID, MAX_ALARM_ID
 ONESHOT_CONF = 'oneshot'
 PERIOD_CONF = 'period'
 
@@ -41,6 +42,8 @@ CONF_TASK_RESTART = 'task_restart'
 CONF_ONSTART = 'onstart'
 CONF_ENV_FILE = 'env_file'
 CONF_CONFLICT = 'conflict'
+CONF_ALARM_ID = 'alarm_id'
+CONF_ALARM_CLEAR_TIME = 'alarm_clear_time'
 
 MOD_FILE_SUFFIX = '.mod'
 MOD_SUFFIX_LEN = 4
@@ -194,6 +197,18 @@ def parse_mod_conf(mod_name, mod_conf):
     task.heartbeat_interval = heartbeat_interval
     task.load_enabled = is_enabled
 
+    try:
+        task.alarm_id = mod_conf.get(CONF_TASK, CONF_ALARM_ID)
+    except configparser.NoOptionError:
+        task.alarm_id = None
+        logging.warning(f"{mod_name} alarm_id not set, alarm_id is None")
+
+    if task.alarm_id is not None:
+        try:
+            task.alarm_clear_time = mod_conf.get(CONF_TASK, CONF_ALARM_CLEAR_TIME)
+        except configparser.NoOptionError:
+            logging.warning(f"{mod_name} not set alarm_clear_time, use 15s as default")
+
     if CONF_ONSTART in mod_conf.options(CONF_TASK):
         is_onstart = (mod_conf.get(CONF_TASK, CONF_ONSTART) == 'yes')
         if task_type == PERIOD_CONF:
@@ -328,3 +343,4 @@ def reload_single_mod(mod_name):
     res, ret = reload_mod_by_name(mod_name)
 
     return res, ret
+
diff --git a/src/python/syssentry/sentryctl b/src/python/syssentry/sentryctl
index e94491f..c2e3cef 100644
--- a/src/python/syssentry/sentryctl
+++ b/src/python/syssentry/sentryctl
@@ -25,6 +25,8 @@ MAX_PARAM_LENGTH = 256
 
 RESULT_MSG_DATA_LEN = 4
 CTL_MSG_LEN_LEN = 3
+ALARM_MSG_DATA_LEN = 6
+DEFAULT_ALARM_TIME_RANGE = 10
 
 def status_output_format(res_data):
     """format output"""
@@ -57,6 +59,8 @@ def res_output_handle(res_struct, req_type):
         status_output_format(res_struct['data'])
     elif req_type == 'get_result':
          result_output_format(res_struct['data'])
+    elif req_type == 'get_alarm':
+         result_output_format(res_struct['data'])
     elif res_struct['ret'] == "failed":
         print(res_struct['data'])
 
@@ -75,6 +79,7 @@ def client_send_and_recv(request_data, data_str_len):
         print("sentryctl: client creat socket error")
         return None
 
+    # connect to syssentry
     try:
         client_socket.connect(CTL_SOCKET_PATH)
     except OSError:
@@ -82,6 +87,7 @@ def client_send_and_recv(request_data, data_str_len):
         print("sentryctl: client connect error")
         return None
 
+    # msg: CTL{len}{data}
     req_data_len = len(request_data)
     request_msg = "CTL" + str(req_data_len).zfill(3) + request_data
 
@@ -94,8 +100,8 @@ def client_send_and_recv(request_data, data_str_len):
         print("sentryctl: client communicate error")
         return None
 
+    # res: RES{len}{data}
     res_magic = res_data[:3]
-
     if res_magic != "RES":
         print("res msg format error")
         return None
@@ -128,6 +134,10 @@ if __name__ == '__main__':
     parser_status.add_argument('task_name')
     parser_get_result = subparsers.add_parser('get_result', help='get task result')
     parser_get_result.add_argument('task_name')
+    parser_get_alarm = subparsers.add_parser('get_alarm', help='get task alarm')
+    parser_get_alarm.add_argument('task_name')
+    parser_get_alarm.add_argument('-s', '--time_range', type=int, default=DEFAULT_ALARM_TIME_RANGE, help='Specified time range')
+    parser_get_alarm.add_argument('-d', '--detailed', action='store_true', help='Print Detailed Information')
     parser_list = subparsers.add_parser('list', help='show all loaded task mod')
 
     client_args = parser.parse_args()
@@ -142,6 +152,17 @@ if __name__ == '__main__':
         req_msg_struct = {"type": "get_status", "data": client_args.task_name}
     elif client_args.cmd_type == 'get_result':
         req_msg_struct = {"type": "get_result", "data": client_args.task_name}
+    elif client_args.cmd_type == 'get_alarm':
+        if not isinstance(client_args.time_range, int) or client_args.time_range <= 0:
+            print(f"time_range is not a positive integer: {client_args.time_range}")
+        req_msg_struct = {
+            "type": "get_alarm", 
+            "data": {
+                'task_name': client_args.task_name, 
+                'time_range': client_args.time_range,
+                'detailed': client_args.detailed,
+            }
+        }
     elif client_args.cmd_type == 'reload':
         req_msg_struct = {"type": "reload", "data": client_args.task_name}
     else:
@@ -155,6 +176,8 @@ if __name__ == '__main__':
     request_message = json.dumps(req_msg_struct)
     if client_args.cmd_type == 'get_result':
         result_message = client_send_and_recv(request_message, RESULT_MSG_DATA_LEN)
+    elif client_args.cmd_type == 'get_alarm':
+        result_message = client_send_and_recv(request_message, ALARM_MSG_DATA_LEN)
     else:
         result_message = client_send_and_recv(request_message, CTL_MSG_LEN_LEN)
     if not result_message:
diff --git a/src/python/syssentry/syssentry.py b/src/python/syssentry/syssentry.py
index 0956e1e..c87256d 100644
--- a/src/python/syssentry/syssentry.py
+++ b/src/python/syssentry/syssentry.py
@@ -28,7 +28,7 @@ from .sentry_config import SentryConfig, get_log_level
 from .task_map import TasksMap
 from .global_values import SENTRY_RUN_DIR, CTL_SOCKET_PATH, SENTRY_RUN_DIR_PERM
 from .cron_process import period_tasks_handle
-from .callbacks import mod_list_show, task_start, task_get_status, task_stop, task_get_result
+from .callbacks import mod_list_show, task_start, task_get_status, task_stop, task_get_result, task_get_alarm
 from .mod_status import get_task_by_pid, set_runtime_status
 from .load_mods import load_tasks, reload_single_mod
 from .heartbeat import (heartbeat_timeout_chk, heartbeat_fd_create,
@@ -36,7 +36,11 @@ from .heartbeat import (heartbeat_timeout_chk, heartbeat_fd_create,
 from .result import RESULT_MSG_HEAD_LEN, RESULT_MSG_MAGIC_LEN, RESULT_MAGIC
 from .result import RESULT_LEVEL_ERR_MSG_DICT, ResultLevel
 from .utils import get_current_time_string
+from .alarm import alarm_register
 
+from xalarm.register_xalarm import xalarm_unregister
+
+clientId = -1
 
 CPU_EXIST = True
 try:
@@ -58,6 +62,7 @@ CTL_MSG_MAGIC_LEN = 3
 CTL_MSG_LEN_LEN = 3
 CTL_MAGIC = "CTL"
 RES_MAGIC = "RES"
+ALARM_MSG_DATA_LEN = 6
 
 CTL_LISTEN_QUEUE_LEN = 5
 SERVER_EPOLL_TIMEOUT = 0.3
@@ -68,6 +73,7 @@ type_func = {
     'stop': task_stop,
     'get_status': task_get_status,
     'get_result': task_get_result,
+    'get_alarm': task_get_alarm,
     'reload': reload_single_mod
 }
 
@@ -260,6 +266,8 @@ def server_recv(server_socket: socket.socket):
     res_head = RES_MAGIC
     if cmd_type == "get_result":
         res_data_len = str(len(res_data)).zfill(RESULT_MSG_HEAD_LEN - RESULT_MSG_MAGIC_LEN)
+    elif cmd_type == "get_alarm":
+        res_data_len = str(len(res_data)).zfill(ALARM_MSG_DATA_LEN)
     else:
         res_data_len = str(len(res_data)).zfill(CTL_MSG_MAGIC_LEN)
     res_head += res_data_len
@@ -643,9 +651,13 @@ def main():
         _ = SentryConfig.init_param()
         TasksMap.init_task_map()
         load_tasks()
+        clientId = alarm_register()
         main_loop()
 
     except Exception:
         logging.error('%s', traceback.format_exc())
     finally:
+        if clientId != -1:
+            xalarm_unregister(clientId)
         release_pidfile()
+
diff --git a/src/python/syssentry/task_map.py b/src/python/syssentry/task_map.py
index 70aa19d..27e97ff 100644
--- a/src/python/syssentry/task_map.py
+++ b/src/python/syssentry/task_map.py
@@ -13,16 +13,16 @@
 tasks map class and initialize function.
 """
 import logging
+from typing import Dict
 
 ONESHOT_TYPE = "ONESHOT"
 PERIOD_TYPE = "PERIOD"
 
 TASKS_MAP = None
 
-
 class TasksMap:
     """task map class"""
-    tasks_dict = {}
+    tasks_dict: Dict[str, Dict] = {}
 
     @classmethod
     def init_task_map(cls):
@@ -65,3 +65,4 @@ class TasksMap:
                 logging.debug("getting task by name: %s", res)
                 break
         return res
+
diff --git a/src/python/xalarm/register_xalarm.py b/src/python/xalarm/register_xalarm.py
index 4f51e66..2a6dabf 100644
--- a/src/python/xalarm/register_xalarm.py
+++ b/src/python/xalarm/register_xalarm.py
@@ -115,6 +115,7 @@ class AlarmRegister:
     def stop_thread(self) -> None:
         self.thread_should_stop = True
         self.thread.join()
+        self.socket.close()
 
 
 def xalarm_register(callback: callable, id_filter: list) -> int:
@@ -147,7 +148,7 @@ def xalarm_unregister(clientId: int) -> None:
     ALARM_REGISTER_INFO = None
 
 
-def xalarm_upgrade(clientId: int, id_filter: list) -> bool:
+def xalarm_upgrade(id_filter: list, clientId: int) -> bool:
     global ALARM_REGISTER_INFO
     if clientId < 0:
         sys.stderr.write("xalarm_upgrade: invalid client\n")
@@ -193,4 +194,3 @@ def xalarm_getdesc(alarm_info: Xalarm) -> str:
     except UnicodeError:
         desc_str = None
     return desc_str
-
diff --git a/src/python/xalarm/xalarm_api.py b/src/python/xalarm/xalarm_api.py
index c365019..863bd02 100644
--- a/src/python/xalarm/xalarm_api.py
+++ b/src/python/xalarm/xalarm_api.py
@@ -98,15 +98,15 @@ class Xalarm:
     def msg1(self, msg):
         """msg1 setter
         """
-        if len(msg) > 512:
-            raise ValueError("msg1 length must below 512")
+        if len(msg) > MAX_MSG_LEN:
+            raise ValueError(f"msg1 length must below {MAX_MSG_LEN}")
         self._msg1 = msg
 
 
 def alarm_bin2stu(bin_data):
     """alarm binary to struct
     """
-    struct_data = struct.unpack("@HBBll512s", bin_data)
+    struct_data = struct.unpack(f"@HBBll{MAX_MSG_LEN}s", bin_data)
 
     alarm_info = Xalarm(1001, 2, 1, 0, 0, "")
     alarm_info.alarm_id = struct_data[0]
diff --git a/src/python/xalarm/xalarm_config.py b/src/python/xalarm/xalarm_config.py
index 8e56d10..754a816 100644
--- a/src/python/xalarm/xalarm_config.py
+++ b/src/python/xalarm/xalarm_config.py
@@ -15,9 +15,10 @@ Create: 2023-11-02
 """
 
 import re
+import os
 import dataclasses
 import logging
-from configparser import ConfigParser
+import configparser
 
 
 MAIN_CONFIG_PATH = '/etc/sysSentry/xalarm.conf'
@@ -27,6 +28,34 @@ MIN_ID_NUMBER = 1001
 MAX_ID_NUMBER = 1128
 MAX_ID_MASK_CAPACITY = 128
 
+# log
+CONF_LOG = 'log'
+CONF_LOG_LEVEL = 'level'
+LogLevel = {
+    "debug": logging.DEBUG,
+    "info": logging.INFO,
+    "warning": logging.WARNING,
+    "error": logging.ERROR,
+    "critical": logging.CRITICAL
+}
+
+
+def get_log_level(filename=MAIN_CONFIG_PATH):
+    if not os.path.exists(filename):
+        return logging.INFO
+
+    try:
+        config = configparser.ConfigParser()
+        config.read(filename)
+        if not config.has_option(CONF_LOG, CONF_LOG_LEVEL):
+            return logging.INFO
+        log_level = config.get(CONF_LOG, CONF_LOG_LEVEL)
+        if log_level.lower() in LogLevel:
+            return LogLevel.get(log_level.lower())
+        return logging.INFO
+    except configparser.Error:
+        return logging.INFO
+
 
 @dataclasses.dataclass
 class AlarmConfig:
@@ -106,7 +135,7 @@ def config_init():
     """
     alarm_config = AlarmConfig()
 
-    cfg = ConfigParser()
+    cfg = configparser.ConfigParser()
     cfg.read(MAIN_CONFIG_PATH)
 
     id_mask = parse_id_mask(cfg)
diff --git a/src/python/xalarm/xalarm_daemon.py b/src/python/xalarm/xalarm_daemon.py
index 00e8886..3ab211c 100644
--- a/src/python/xalarm/xalarm_daemon.py
+++ b/src/python/xalarm/xalarm_daemon.py
@@ -21,7 +21,7 @@ import signal
 import fcntl
 import socket
 
-from .xalarm_config import config_init
+from .xalarm_config import config_init, get_log_level
 from .xalarm_server import server_loop, SOCK_FILE
 
 ALARM_DIR = "/var/run/xalarm"
@@ -120,9 +120,10 @@ def alarm_process_create():
         os.mkdir(ALARM_DIR)
         os.chmod(ALARM_DIR, ALARM_DIR_PERMISSION)
 
+    log_level = get_log_level()
+    log_format = "%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s"
 
-    logging.basicConfig(filename=ALARM_LOGFILE, level=logging.INFO,
-                        format='%(asctime)s|%(levelname)s| %(message)s')
+    logging.basicConfig(filename=ALARM_LOGFILE, level=log_level, format=log_format)
 
     signal.signal(signal.SIGTERM, signal_handler)
 
diff --git a/src/python/xalarm/xalarm_transfer.py b/src/python/xalarm/xalarm_transfer.py
index 010d2ee..d135095 100644
--- a/src/python/xalarm/xalarm_transfer.py
+++ b/src/python/xalarm/xalarm_transfer.py
@@ -62,7 +62,6 @@ def cleanup_closed_connections(server_sock, epoll, fd_to_socket):
             to_remove.append(fileno)
     
     for fileno in to_remove:
-        epoll.unregister(fileno)
         fd_to_socket[fileno].close()
         del fd_to_socket[fileno]
         logging.info(f"cleaned up connection {fileno} for client lost connection.")
@@ -97,13 +96,12 @@ def wait_for_connection(server_sock, epoll, fd_to_socket, thread_should_stop):
                         logging.info(f"connection reach max num of {MAX_CONNECTION_NUM}, closed current connection!")
                         connection.close()
                         continue
-                    epoll.register(connection.fileno(), select.EPOLLOUT)
                     fd_to_socket[connection.fileno()] = connection
         except socket.error as e: 
-            logging.debug("socket error, reason is %s", e)
+            logging.debug(f"socket error, reason is {e}")
             break
         except (KeyError, OSError, ValueError) as e:
-            logging.debug("wait for connection failed %s", e)
+            logging.debug(f"wait for connection failed {e}")
 
 
 def transmit_alarm(server_sock, epoll, fd_to_socket, bin_data):
@@ -122,7 +120,7 @@ def transmit_alarm(server_sock, epoll, fd_to_socket, bin_data):
             except (BrokenPipeError, ConnectionResetError):
                 to_remove.append(fileno)
     for fileno in to_remove:
-        epoll.unregister(fileno)
         fd_to_socket[fileno].close()
         del fd_to_socket[fileno]
         logging.info(f"cleaned up connection {fileno} for client lost connection.")
+
-- 
2.43.0

