diff --git a/.env b/.env
new file mode 100644
index 0000000..262d663
--- /dev/null
+++ b/.env
@@ -0,0 +1,11 @@
+log_level=DEBUG
+
+# sqlite连接方式:sqlite:///
+database_url=sqlite:///./logs.db
+
+wechat_webhook_url=https://qyapi.weixin.qq.com/cgi-bin/webhook/send
+wechat_webhook_key=f09eb098-f709-4fc6-83b5-4a08a8431b8f
+
+openobserve_url=http://172.29.100.41:5080/api/default/_search
+openobserve_username=developer@estun.com
+openobserve_password=developer123
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..3dce9c6
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/log-alert-service.iml b/.idea/log-alert-service.iml
new file mode 100644
index 0000000..b9dcfb8
--- /dev/null
+++ b/.idea/log-alert-service.iml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..d5b6360
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..d88adc6
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/__pycache__/main.cpython-310.pyc b/__pycache__/main.cpython-310.pyc
new file mode 100644
index 0000000..fd24eb6
Binary files /dev/null and b/__pycache__/main.cpython-310.pyc differ
diff --git a/apis/__pycache__/__init__.cpython-310.pyc b/apis/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000..aa96731
Binary files /dev/null and b/apis/__pycache__/__init__.cpython-310.pyc differ
diff --git a/apis/__pycache__/log.cpython-310.pyc b/apis/__pycache__/log.cpython-310.pyc
new file mode 100644
index 0000000..656dd7c
Binary files /dev/null and b/apis/__pycache__/log.cpython-310.pyc differ
diff --git a/apis/log.py b/apis/log.py
index 1bfcc1d..7acd199 100644
--- a/apis/log.py
+++ b/apis/log.py
@@ -1,5 +1,7 @@
-from fastapi import APIRouter
+from fastapi import APIRouter, Depends
+from sqlalchemy.orm import Session
+from config.database import get_db
from schemas.log_alert import LogAlertTrigger
from service import log_alert_service
@@ -10,6 +12,11 @@ router = APIRouter(
)
+@router.get("", summary="查询日志告警")
+def query_alert(db: Session = Depends(get_db)):
+ return log_alert_service.query_alert(db)
+
+
@router.post("", summary="触发日志告警")
-def trigger_log_alter(log_alter: LogAlertTrigger):
- return log_alert_service.trigger_alert(log_alter)
+def trigger_log_alter(log_alter: LogAlertTrigger, db: Session = Depends(get_db)) -> bool:
+ return log_alert_service.trigger_alert(log_alter, db)
diff --git a/config/__pycache__/database.cpython-310.pyc b/config/__pycache__/database.cpython-310.pyc
new file mode 100644
index 0000000..0c1b3f5
Binary files /dev/null and b/config/__pycache__/database.cpython-310.pyc differ
diff --git a/config/__pycache__/setting.cpython-310.pyc b/config/__pycache__/setting.cpython-310.pyc
new file mode 100644
index 0000000..f918920
Binary files /dev/null and b/config/__pycache__/setting.cpython-310.pyc differ
diff --git a/config/setting.py b/config/setting.py
index 1ee8784..63c5c1a 100644
--- a/config/setting.py
+++ b/config/setting.py
@@ -2,17 +2,16 @@ from pydantic_settings import BaseSettings
class Settings(BaseSettings):
- # sqlite连接方式:sqlite:///
- database_url: str = "sqlite:///./logs.db"
- wechat_webhook: str = ""
- smtp_server: str = ""
- smtp_port: int = 587
- smtp_user: str = ""
- smtp_password: str = ""
- smtp_sender: str = "log-alert@example.com"
+ log_level: str
+ database_url: str
+ wechat_webhook_url: str
+ wechat_webhook_key: str
+ openobserve_url: str
+ openobserve_username: str
+ openobserve_password: str
class Config:
env_file = ".env"
-settings = Settings()
\ No newline at end of file
+settings = Settings()
diff --git a/id_generator/__init__.py b/id_generator/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/id_generator/__pycache__/__init__.cpython-312.pyc b/id_generator/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 934c604..0000000
Binary files a/id_generator/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/id_generator/__pycache__/generator.cpython-312.pyc b/id_generator/__pycache__/generator.cpython-312.pyc
deleted file mode 100644
index 3ef7f83..0000000
Binary files a/id_generator/__pycache__/generator.cpython-312.pyc and /dev/null differ
diff --git a/id_generator/__pycache__/options.cpython-312.pyc b/id_generator/__pycache__/options.cpython-312.pyc
deleted file mode 100644
index 781eaf9..0000000
Binary files a/id_generator/__pycache__/options.cpython-312.pyc and /dev/null differ
diff --git a/id_generator/__pycache__/snowflake.cpython-312.pyc b/id_generator/__pycache__/snowflake.cpython-312.pyc
deleted file mode 100644
index 6fca833..0000000
Binary files a/id_generator/__pycache__/snowflake.cpython-312.pyc and /dev/null differ
diff --git a/id_generator/__pycache__/snowflake_m1.cpython-312.pyc b/id_generator/__pycache__/snowflake_m1.cpython-312.pyc
deleted file mode 100644
index 9e662f0..0000000
Binary files a/id_generator/__pycache__/snowflake_m1.cpython-312.pyc and /dev/null differ
diff --git a/id_generator/generator.py b/id_generator/generator.py
deleted file mode 100644
index fc995d7..0000000
--- a/id_generator/generator.py
+++ /dev/null
@@ -1,38 +0,0 @@
-"""
-雪花算法生成器IdGenerator
-"""
-
-# !/usr/bin/python
-# coding=UTF-8
-
-
-from . import options
-from . import snowflake_m1
-
-
-class DefaultIdGenerator:
- """
- ID生成器
- """
-
- def __init__(self):
- self.snowflake = None
-
- def set_id_generator(self, option: options.IdGeneratorOptions):
- """
- 设置id生成规则信息
- """
-
- if option.base_time < 100000:
- raise ValueError("base time error.")
-
- self.snowflake = snowflake_m1.SnowFlakeM1(option)
-
- def next_id(self) -> int:
- """
- 获取新的UUID
- """
-
- if self.snowflake is None:
- raise ValueError("please set id generator at first.")
- return self.snowflake.next_id()
diff --git a/id_generator/idregister.py b/id_generator/idregister.py
deleted file mode 100644
index 787faac..0000000
--- a/id_generator/idregister.py
+++ /dev/null
@@ -1,134 +0,0 @@
-"""
-worker id generator
-"""
-
-# !/usr/bin/python
-# coding=UTF-8
-
-
-from threading import Thread
-import time
-import logging
-import redis
-
-
-class Register:
- """
- redis封装
- - host 代表redis ip
- - port 代表redis端口
- - max_worker_id worker_id的最大值, 默认为100
- - password redis的密码, 默认为空
- """
-
- def __init__(self, host, port, max_worker_id=100, password=None):
- self.redis_impl = redis.StrictRedis(host=host, port=port, db=0, password=password)
- self.loop_count = 0
- self.max_loop_count = 10
- self.worker_id_expire_time = 15
- self.max_worker_id = max_worker_id
- self.worker_id = -1
- self.is_stop = False
-
- def get_lock(self, key):
- """
- 获取分布式全局锁,并设置过期时间为30秒
- """
-
- if self.redis_impl.setnx(key, 1):
- self.redis_impl.expire(key, 30)
- return True
- if self.redis_impl.ttl(key) < 0:
- self.redis_impl.expire(key, 30)
- return False
-
- def stop(self):
- """
- 退出注册器的线程
- """
-
- self.is_stop = True
-
- def get_worker_id(self):
- """
- 获取全局唯一worker_id, 会创建一个线程给worker id续期
- 失败返回-1
- """
-
- self.loop_count = 0
-
- def extern_life(my_id):
- while 1:
- time.sleep(self.worker_id_expire_time / 3)
- # 是否关闭了
- if self.is_stop:
- return
- # 更新生命周期
- if self.worker_id != my_id:
- break
- try:
- self.redis_impl.expire(
- f"IdGen:WorkerId:Value:{my_id}",
- self.worker_id_expire_time)
- except Exception as exe:
- logging.error(exe)
- continue
-
- self.worker_id = self.__get_next_worker_id()
- if self.worker_id > -1:
- Thread(target=extern_life, args=[self.worker_id]).start()
- return self.worker_id
-
- def __get_next_worker_id(self):
- """
- 获取全局唯一worker id内部实现
- """
-
- cur = self.redis_impl.incrby("IdGen:WorkerId:Index", 1)
-
- def can_reset():
- try:
- reset_value = self.redis_impl.incr("IdGen:WorkerId:Value:Edit")
- return reset_value != 1
- except Exception as ept:
- logging.error(ept)
- return False
-
- def end_reset():
- try:
- self.redis_impl.set("IdGen:WorkerId:Value:Edit", 0)
- except Exception as ept:
- logging.error(ept)
-
- def is_available(worker_id: int):
- try:
- rst = self.redis_impl.get(f"IdGen:WorkerId:Value:{worker_id}")
- return rst != "Y"
- except Exception as ept:
- logging.error(ept)
- return False
-
- if cur > self.max_worker_id:
- if can_reset():
- self.redis_impl.set("IdGen:WorkerId:Index", -1)
- end_reset()
- self.loop_count += 1
-
- if self.loop_count > self.max_loop_count:
- self.loop_count = 0
- return -1
-
- time.sleep(0.2 * self.loop_count)
- return self.__get_next_worker_id()
- time.sleep(0.2)
- return self.__get_next_worker_id()
- if is_available(cur):
- self.redis_impl.setex(
- f"IdGen:WorkerId:Value:{cur}",
- self.worker_id_expire_time,
- "Y"
- )
- self.loop_count = 0
- return cur
-
- return self.__get_next_worker_id()
diff --git a/id_generator/options.py b/id_generator/options.py
deleted file mode 100644
index 8558381..0000000
--- a/id_generator/options.py
+++ /dev/null
@@ -1,43 +0,0 @@
-"""
-生成器IdGenerator配置选项
-"""
-
-# !/usr/bin/python
-# coding=UTF-8
-
-
-class IdGeneratorOptions:
- """
- ID生成器配置
- - worker_id 全局唯一id, 区分不同uuid生成器实例
- - worker_id_bit_length 生成的uuid中worker_id占用的位数
- - seq_bit_length 生成的uuid中序列号占用的位数
- """
-
- def __init__(self, worker_id=0, worker_id_bit_length=6, seq_bit_length=6):
-
- # 雪花计算方法,(1-漂移算法|2-传统算法), 默认1。目前只实现了1。
- self.method = 1
-
- # 基础时间(ms单位), 不能超过当前系统时间
- self.base_time = 1582136402000
-
- # 机器码, 必须由外部设定, 最大值 2^worker_id_bit_length-1
- self.worker_id = worker_id
-
- # 机器码位长, 默认值6, 取值范围 [1, 15](要求:序列数位长+机器码位长不超过22)
- self.worker_id_bit_length = worker_id_bit_length
-
- # 序列数位长, 默认值6, 取值范围 [3, 21](要求:序列数位长+机器码位长不超过22)
- self.seq_bit_length = seq_bit_length
-
- # 最大序列数(含), 设置范围 [max_seq_number, 2^seq_bit_length-1]
- # 默认值0, 表示最大序列数取最大值(2^seq_bit_length-1])
- self.max_seq_number = 0
-
- # 最小序列数(含), 默认值5, 取值范围 [5, max_seq_number], 每毫秒的前5个序列数对应编号0-4是保留位
- # 其中1-4是时间回拨相应预留位, 0是手工新值预留位
- self.min_seq_number = 5
-
- # 最大漂移次数(含), 默认2000, 推荐范围500-10000(与计算能力有关)
- self.top_over_cost_count = 2000
diff --git a/id_generator/snowflake.py b/id_generator/snowflake.py
deleted file mode 100644
index ea36cfa..0000000
--- a/id_generator/snowflake.py
+++ /dev/null
@@ -1,20 +0,0 @@
-
-"""
-雪花算法生成器接口声明
-"""
-
-# !/usr/bin/python
-# coding=UTF-8
-
-
-class SnowFlake():
-
- def __init__(self, options):
- self.options = options
-
- def next_id(self) -> int:
- """
- 获取新的UUID
- """
-
- return 0
diff --git a/id_generator/snowflake_m1.py b/id_generator/snowflake_m1.py
deleted file mode 100644
index 596b76d..0000000
--- a/id_generator/snowflake_m1.py
+++ /dev/null
@@ -1,147 +0,0 @@
-"""
-M1生成器
-"""
-
-# !/usr/bin/python
-# coding=UTF-8
-
-import threading
-import time
-from .snowflake import SnowFlake
-from .options import IdGeneratorOptions
-
-
-class SnowFlakeM1(SnowFlake):
- """
- M1规则ID生成器配置
- """
-
- def __init__(self, options: IdGeneratorOptions):
- # 1.base_time
- self.base_time = 1582136402000
- if options.base_time != 0:
- self.base_time = int(options.base_time)
-
- # 2.worker_id_bit_length
- self.worker_id_bit_length = 6
- if options.worker_id_bit_length != 0:
- self.worker_id_bit_length = int(options.worker_id_bit_length)
-
- # 3.worker_id
- self.worker_id = options.worker_id
-
- # 4.seq_bit_length
- self.seq_bit_length = 6
- if options.seq_bit_length != 0:
- self.seq_bit_length = int(options.seq_bit_length)
-
- # 5.max_seq_number
- self.max_seq_number = int(options.max_seq_number)
- if options.max_seq_number <= 0:
- self.max_seq_number = (1 << self.seq_bit_length) - 1
-
- # 6.min_seq_number
- self.min_seq_number = int(options.min_seq_number)
-
- # 7.top_over_cost_count
- self.top_over_cost_count = int(options.top_over_cost_count)
-
- # 8.Others
- self.__timestamp_shift = self.worker_id_bit_length + self.seq_bit_length
- self.__current_seq_number = self.min_seq_number
- self.__last_time_tick: int = 0
- self.__turn_back_time_tick: int = 0
- self.__turn_back_index: int = 0
- self.__is_over_cost = False
- self.___over_cost_count_in_one_term: int = 0
- self.__id_lock = threading.Lock()
-
- def __next_over_cost_id(self) -> int:
- current_time_tick = self.__get_current_time_tick()
- if current_time_tick > self.__last_time_tick:
- self.__last_time_tick = current_time_tick
- self.__current_seq_number = self.min_seq_number
- self.__is_over_cost = False
- self.___over_cost_count_in_one_term = 0
- return self.__calc_id(self.__last_time_tick)
-
- if self.___over_cost_count_in_one_term >= self.top_over_cost_count:
- self.__last_time_tick = self.__get_next_time_tick()
- self.__current_seq_number = self.min_seq_number
- self.__is_over_cost = False
- self.___over_cost_count_in_one_term = 0
- return self.__calc_id(self.__last_time_tick)
-
- if self.__current_seq_number > self.max_seq_number:
- self.__last_time_tick += 1
- self.__current_seq_number = self.min_seq_number
- self.__is_over_cost = True
- self.___over_cost_count_in_one_term += 1
- return self.__calc_id(self.__last_time_tick)
-
- return self.__calc_id(self.__last_time_tick)
-
- def __next_normal_id(self) -> int:
- current_time_tick = self.__get_current_time_tick()
- if current_time_tick < self.__last_time_tick:
- if self.__turn_back_time_tick < 1:
- self.__turn_back_time_tick = self.__last_time_tick - 1
- self.__turn_back_index += 1
- # 每毫秒序列数的前5位是预留位, 0用于手工新值, 1-4是时间回拨次序
- # 支持4次回拨次序(避免回拨重叠导致ID重复), 可无限次回拨(次序循环使用)。
- if self.__turn_back_index > 4:
- self.__turn_back_index = 1
-
- return self.__calc_turn_back_id(self.__turn_back_time_tick)
-
- # 时间追平时, _TurnBackTimeTick清零
- self.__turn_back_time_tick = min(self.__turn_back_time_tick, 0)
-
- if current_time_tick > self.__last_time_tick:
- self.__last_time_tick = current_time_tick
- self.__current_seq_number = self.min_seq_number
- return self.__calc_id(self.__last_time_tick)
-
- if self.__current_seq_number > self.max_seq_number:
- self.__last_time_tick += 1
- self.__current_seq_number = self.min_seq_number
- self.__is_over_cost = True
- self.___over_cost_count_in_one_term = 1
- return self.__calc_id(self.__last_time_tick)
-
- return self.__calc_id(self.__last_time_tick)
-
- def __calc_id(self, use_time_tick) -> int:
- self.__current_seq_number += 1
- return (
- (use_time_tick << self.__timestamp_shift) +
- (self.worker_id << self.seq_bit_length) +
- self.__current_seq_number
- ) % int(1e64)
-
- def __calc_turn_back_id(self, use_time_tick) -> int:
- self.__turn_back_time_tick -= 1
- return (
- (use_time_tick << self.__timestamp_shift) +
- (self.worker_id << self.seq_bit_length) +
- self.__turn_back_index
- ) % int(1e64)
-
- def __get_current_time_tick(self) -> int:
- return int((time.time_ns() / 1e6) - self.base_time)
-
- def __get_next_time_tick(self) -> int:
- temp_time_ticker = self.__get_current_time_tick()
- while temp_time_ticker <= self.__last_time_tick:
- # 0.001 = 1 mili sec
- time.sleep(0.001)
- temp_time_ticker = self.__get_current_time_tick()
- return temp_time_ticker
-
- def next_id(self) -> int:
- with self.__id_lock:
- if self.__is_over_cost:
- nextid = self.__next_over_cost_id()
- else:
- nextid = self.__next_normal_id()
- return nextid
diff --git a/logs.db b/logs.db
index 763fad3..6c380f9 100644
Binary files a/logs.db and b/logs.db differ
diff --git a/models/__pycache__/base.cpython-312.pyc b/models/__pycache__/base.cpython-312.pyc
deleted file mode 100644
index bb5d27c..0000000
Binary files a/models/__pycache__/base.cpython-312.pyc and /dev/null differ
diff --git a/models/__pycache__/log_alert.cpython-310.pyc b/models/__pycache__/log_alert.cpython-310.pyc
new file mode 100644
index 0000000..a77da78
Binary files /dev/null and b/models/__pycache__/log_alert.cpython-310.pyc differ
diff --git a/models/base.py b/models/base.py
deleted file mode 100644
index d35671e..0000000
--- a/models/base.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from sqlalchemy import Column, BigInteger, DateTime, event
-from sqlalchemy.ext.declarative import declared_attr, declarative_base
-
-from datetime import datetime
-
-from config.database import Base
-from utils.common import camel_to_snake
-from id_generator import options, generator
-
-# https://github.com/yitter/IdGenerator/tree/master/Python
-options = options.IdGeneratorOptions(worker_id=23)
-idgen = generator.DefaultIdGenerator()
-idgen.set_id_generator(options)
-
-
-# 第二层基类:包含ID
-class IdBase(Base):
- __abstract__ = True
-
- id = Column(BigInteger, primary_key=True, index=True)
-
- @declared_attr
- def __tablename__(cls):
- # 自动把数据库实体类名驼峰转为数据库表名下划线
- return camel_to_snake(cls.__name__)
-
-
-# 自动填充id
-@event.listens_for(IdBase, 'before_insert', propagate=True)
-def before_insert_listener(mapper, connection, target):
- if target.id is None:
- target.id = idgen.next_id()
-
-
-# 第二层基类:包含ID和审计字段
-class AuditBase(IdBase):
- __abstract__ = True
-
- create_time = Column(DateTime, nullable=True, default=datetime.now)
- update_time = Column(DateTime, nullable=True, default=datetime.now, onupdate=datetime.now)
diff --git a/models/log_alert.py b/models/log_alert.py
index 6d8f97e..791d8ad 100644
--- a/models/log_alert.py
+++ b/models/log_alert.py
@@ -1,7 +1,8 @@
-from sqlalchemy import Column, Integer, String, DateTime, Text
-from sqlalchemy.ext.declarative import declarative_base
from datetime import datetime
+from sqlalchemy import Column, Integer, String, Text, DateTime
+from sqlalchemy.ext.declarative import declarative_base
+
Base = declarative_base()
@@ -9,9 +10,12 @@ class LogAlert(Base):
__tablename__ = "log_alerts"
id = Column(Integer, primary_key=True, index=True)
- timestamp = Column(DateTime)
- log_level = Column(String(20))
+ alter_name = Column(String(50))
+ alter_timestamp = Column(Integer)
+
+ timestamp = Column(Integer)
message = Column(Text)
- source = Column(String(100))
- context = Column(Text) # 存储JSON格式的上下文信息
status = Column(String(20), default="pending") # pending, notified, resolved
+
+ create_time = Column(DateTime, nullable=True, default=datetime.now)
+ update_time = Column(DateTime, nullable=True, default=datetime.now, onupdate=datetime.now)
diff --git a/requirements.txt b/requirements.txt
index 36adcf2..ad92be6 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1 @@
-fastapi~=0.119.0
pydantic~=2.12.2
SQLAlchemy~=2.0.44
requests~=2.32.5
\ No newline at end of file
+fastapi~=0.119.0
pydantic~=2.12.2
SQLAlchemy~=2.0.44
requests~=2.32.5
pydantic-settings~=2.11.0
uvicorn~=0.38.0
\ No newline at end of file
diff --git a/schemas/__pycache__/log_alert.cpython-310.pyc b/schemas/__pycache__/log_alert.cpython-310.pyc
new file mode 100644
index 0000000..2c2bf8a
Binary files /dev/null and b/schemas/__pycache__/log_alert.cpython-310.pyc differ
diff --git a/service/__pycache__/log_alert_service.cpython-310.pyc b/service/__pycache__/log_alert_service.cpython-310.pyc
new file mode 100644
index 0000000..e6c1bb9
Binary files /dev/null and b/service/__pycache__/log_alert_service.cpython-310.pyc differ
diff --git a/service/__pycache__/message_service.cpython-310.pyc b/service/__pycache__/message_service.cpython-310.pyc
new file mode 100644
index 0000000..4fabce2
Binary files /dev/null and b/service/__pycache__/message_service.cpython-310.pyc differ
diff --git a/service/__pycache__/openobserve_service.cpython-310.pyc b/service/__pycache__/openobserve_service.cpython-310.pyc
new file mode 100644
index 0000000..b8a368f
Binary files /dev/null and b/service/__pycache__/openobserve_service.cpython-310.pyc differ
diff --git a/service/log_alert_service.py b/service/log_alert_service.py
index 2174280..1c91d37 100644
--- a/service/log_alert_service.py
+++ b/service/log_alert_service.py
@@ -1,31 +1,70 @@
-import requests
-from requests.auth import HTTPBasicAuth
+from collections import defaultdict
-from schemas.log_alert import LogAlertTrigger, OpenobserveQuery
+from sqlalchemy import insert, select
+from sqlalchemy.orm import Session
+
+from models.log_alert import LogAlert
+from schemas.log_alert import LogAlertTrigger
from service import openobserve_service
+from service.message_service import send_wechat_message, MessageEnum
+from utils.time import get_timestamp_range, format_timestamp
-def trigger_alert(log_alter: LogAlertTrigger):
- #print(log_alter)
- # openobserve_service.get_log()
+def trigger_alert(log_alter: LogAlertTrigger, db: Session) -> bool:
+ start_time, end_time = get_timestamp_range(log_alter.timestamp, 60)
+ result = openobserve_service.get_log(start_time, end_time)
+ hits = result.get("hits")
- url = 'http://192.168.1.7:5080/api/default/_search'
- username = 'njcxx0822@163.com'
- password = '19940822Cxx'
+ # 判断日志是否为空
+ if len(hits) == 0:
+ return False
- response = requests.post(
- url,
- headers={
- 'accept': 'application/json',
- 'Content-Type': 'application/json'
- },
- json={
- "query": OpenobserveQuery(
- start_time=1760773184953420,
- end_time=1760773189953431
- ).model_dump()
- },
- auth=HTTPBasicAuth(username, password)
+ # 判断日志告警是否已经存在
+ log_db_alter = db.execute(
+ select(LogAlert)
+ .where(LogAlert.alter_timestamp == log_alter.timestamp)
+ ).first()
+
+ if log_db_alter is not None:
+ return False
+
+ # 企业微信通知
+ alter_message = f"🔔 **告警服务**:{log_alter.alter_name} \n 🕒 **告警时间**:{format_timestamp(log_alter.timestamp)}"
+ send_wechat_message(MessageEnum.MARKDOWN2, alter_message)
+
+ # 批量插入
+ return batch_insert(log_alter, hits, db)
+
+
+def batch_insert(log_alter: LogAlertTrigger, hits: [], db: Session) -> bool:
+ stmt = insert(LogAlert).values(
+ [
+ {
+ "alter_name": log_alter.alter_name,
+ "alter_timestamp": log_alter.timestamp,
+ "timestamp": hit["_timestamp"],
+ "message": hit["message"],
+ "status": "pending"
+ }
+ for hit in hits
+ ]
)
- return response.json().get("hits")
+ db.execute(stmt)
+ db.commit()
+
+ return True
+
+
+def query_alert(db: Session):
+ alerts = db.execute(select(LogAlert)).scalars().all()
+
+ # 按照name和timestamp分类
+ classified = defaultdict(lambda: defaultdict(list))
+ for alert in alerts:
+ classified[alert.alter_name][alert.alter_timestamp].append({
+ "timestamp": format_timestamp(alert.timestamp),
+ "message": alert.message
+ })
+
+ return dict(classified)
diff --git a/service/message_service.py b/service/message_service.py
new file mode 100644
index 0000000..c10d057
--- /dev/null
+++ b/service/message_service.py
@@ -0,0 +1,32 @@
+import json
+from enum import Enum
+
+import requests
+
+from config.setting import settings
+
+
+class MessageEnum(Enum):
+ TEXT = 'text'
+ MARKDOWN = 'markdown'
+ MARKDOWN2 = 'markdown2'
+
+
+def send_wechat_message(message_type: MessageEnum, message: str):
+ wechat_message = {}
+ match message_type:
+ case MessageEnum.TEXT:
+ wechat_message = {"msgtype": "text", "text": {"content": message}}
+ case MessageEnum.MARKDOWN:
+ wechat_message = {"msgtype": "markdown", "markdown": {"content": message}}
+ case MessageEnum.MARKDOWN2:
+ wechat_message = {"msgtype": "markdown_v2", "markdown_v2": {"content": message}}
+
+ requests.post(
+ settings.wechat_webhook_url,
+ headers={"Content-Type": "application/json"},
+ params={'key': settings.wechat_webhook_key},
+ data=json.dumps(wechat_message, ensure_ascii=False).encode('utf-8')
+ )
+
+ return True
diff --git a/service/openobserve_service.py b/service/openobserve_service.py
index 961d813..14ef81b 100644
--- a/service/openobserve_service.py
+++ b/service/openobserve_service.py
@@ -1,27 +1,19 @@
import requests
from requests.auth import HTTPBasicAuth
+from config.setting import settings
from schemas.log_alert import OpenobserveQuery
-def get_log():
- url = 'http://192.168.1.7:5080/api/default/_search'
- username = 'njcxx0822@163.com'
- password = '19940822Cxx'
-
+def get_log(start_time: int, end_time: int) -> str:
response = requests.post(
- url,
+ settings.openobserve_url,
headers={
'accept': 'application/json',
'Content-Type': 'application/json'
},
- json={
- "query": OpenobserveQuery(
- start_time=1760773184953420,
- end_time=1760773189953431
- ).model_dump()
- },
- auth=HTTPBasicAuth(username, password)
+ json={"query": OpenobserveQuery(start_time=start_time, end_time=end_time).model_dump()},
+ auth=HTTPBasicAuth(settings.openobserve_username, settings.openobserve_password)
)
- print(response)
+ return response.json()
diff --git a/utils/__pycache__/time.cpython-310.pyc b/utils/__pycache__/time.cpython-310.pyc
new file mode 100644
index 0000000..ef68244
Binary files /dev/null and b/utils/__pycache__/time.cpython-310.pyc differ
diff --git a/utils/time.py b/utils/time.py
new file mode 100644
index 0000000..0aac705
--- /dev/null
+++ b/utils/time.py
@@ -0,0 +1,25 @@
+from datetime import datetime
+from typing import Tuple
+
+
+def get_timestamp_range(ts: int, delta_seconds: int = 5, unit: str = "microseconds") -> Tuple[int, int]:
+ """
+ 返回时间戳前后delta_seconds秒的范围(单位与输入一致)
+ """
+ if ts <= 0:
+ return 0, 0
+
+ scale = {
+ "seconds": 1,
+ "milliseconds": 1000,
+ "microseconds": 1_000_000,
+ "nanoseconds": 1_000_000_000
+
+ }.get(unit, 1_000_000) # 默认微秒
+
+ delta = delta_seconds * scale
+ return ts - delta, ts + delta
+
+
+def format_timestamp(timestamp: int) -> str:
+ return datetime.fromtimestamp(timestamp / 1_000_000).strftime("%Y-%m-%d %H:%M:%S")