/ AIG-PromptSecurity / cli / aig_logger.py
aig_logger.py
  1  # Copyright (c) 2024-2026 Tencent Zhuque Lab. All rights reserved.
  2  #
  3  # Licensed under the Apache License, Version 2.0 (the "License");
  4  # you may not use this file except in compliance with the License.
  5  # You may obtain a copy of the License at
  6  #
  7  #     http://www.apache.org/licenses/LICENSE-2.0
  8  #
  9  # Unless required by applicable law or agreed to in writing, software
 10  # distributed under the License is distributed on an "AS IS" BASIS,
 11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  # See the License for the specific language governing permissions and
 13  # limitations under the License.
 14  #
 15  # Requirement: Any integration or derivative work must explicitly attribute
 16  # Tencent Zhuque Lab (https://github.com/Tencent/AI-Infra-Guard) in its
 17  # documentation or user interface, as detailed in the NOTICE file.
 18  
 19  import sys
 20  import gettext
 21  import os
 22  from typing import Literal, Union
 23  from datetime import datetime
 24  from pydantic import BaseModel
 25  from loguru import logger as base_logger
 26  
 27  class contentSchema(BaseModel):
 28      timestamp: str | None = None
 29  
 30  class newPlanStep(contentSchema):
 31      stepId: str
 32      title: str
 33  
 34  class statusUpdate(contentSchema):
 35      stepId: str
 36      brief: str
 37      description: str
 38      status: Literal["running", "completed", "failed"]
 39  
 40  class toolUsed(contentSchema):
 41      stepId: str
 42      tool_id: str
 43      tool_name: str | None = None
 44      brief: str
 45      status: Literal["todo", "doing", "done"]
 46  
 47  class actionLog(contentSchema):
 48      tool_id: str
 49      tool_name: str
 50      stepId: str
 51      log: str
 52  
 53  class resultUpdate(contentSchema):
 54      msgType: Literal["text", "markdown", "file", "json"]
 55      content: str | dict | list
 56      status: bool | None = None
 57  
 58  class PromptSecurityLog(BaseModel):
 59      type: Literal["error", "newPlanStep", "statusUpdate", "toolUsed", "actionLog", "resultUpdate"]
 60      content: Union[str, newPlanStep, statusUpdate, toolUsed, actionLog, resultUpdate]
 61  
 62  class PromptSecurityLogger:
 63      def __init__(self, base_logger, lang='en_US'):
 64          self._base_logger = base_logger
 65          self._base_logger.remove()
 66          self._base_logger.add(sys.stdout, filter=lambda record: not record["extra"].get("aig_log", False), level="DEBUG")
 67          self._base_logger.add(sys.stdout, filter=lambda record: record["extra"].get("aig_log", False), format="{message}")
 68          self.lang = lang
 69          self._setup_i18n()
 70      
 71      def add(self, *args, **kwargs):
 72          self._base_logger.add(*args, **kwargs)
 73  
 74      def info(self, *args, **kwargs):
 75          self._base_logger.opt(depth=1).info(*args, **kwargs)
 76  
 77      def debug(self, *args, **kwargs):
 78          self._base_logger.opt(depth=1).debug(*args, **kwargs)
 79  
 80      def warning(self, *args, **kwargs):
 81          self._base_logger.opt(depth=1).warning(*args, **kwargs)
 82  
 83      def error(self, *args, **kwargs):
 84          self._base_logger.opt(depth=1).error(*args, **kwargs)
 85      
 86      def exception(self, *args, **kwargs):
 87          self._base_logger.opt(depth=1).exception(*args, **kwargs)
 88  
 89      def disable(self):
 90          self._base_logger.disable("")
 91      
 92      def enable(self):
 93          self._base_logger.enable("")
 94  
 95      def _setup_i18n(self):
 96          localedir = os.path.join(os.path.dirname(__file__), 'locales')
 97          try:
 98              self.trans = gettext.translation('messages', localedir=localedir, languages=[self.lang])
 99              self.lang = self.trans.info().get("language", self.lang)
100              self.trans.install()
101              self._ = self.trans.gettext
102          except FileNotFoundError:
103              gettext.install('messages')
104              self._ = gettext.gettext
105      
106      def set_language(self, lang):
107          """动态切换语言"""
108          self.lang = lang
109          self._setup_i18n()
110  
111      def translated_msg(self, msg, *args, **kwargs):
112          translated_msg = self._(msg)
113          if args or kwargs:
114              translated_msg = translated_msg.format(*args, **kwargs)
115          return translated_msg
116  
117      def _create_log(self, log_type: str, content: Union[str, contentSchema]) -> str:
118          """创建符合PromptSecurityLog格式的日志"""
119          if isinstance(content, contentSchema) and "timestamp" not in content:
120              content.timestamp = datetime.now().isoformat()
121          
122          log_entry = PromptSecurityLog(
123              type=log_type,
124              content=content
125          )
126          return log_entry.model_dump_json(exclude_none=True)
127      
128      def log(self, log_type: str, content: Union[str, contentSchema]):
129          """记录日志"""
130          log_message = self._create_log(log_type, content)
131          self._base_logger.bind(aig_log=True).opt(depth=2).log("INFO", "\n" + log_message)
132      
133      # 为每种日志类型创建便捷方法
134      def critical_issue(self, content: str):
135          self.log("error", content)
136          
137      def new_plan_step(self, content: newPlanStep):
138          self.log("newPlanStep", content)
139      
140      def status_update(self, content: statusUpdate):
141          self.log("statusUpdate", content)
142      
143      def tool_used(self, content: toolUsed):
144          self.log("toolUsed", content)
145      
146      def action_log(self, content: actionLog):
147          self.log("actionLog", content)
148      
149      def result_update(self, content: resultUpdate):
150          self.log("resultUpdate", content)
151  
152  logger = PromptSecurityLogger(base_logger)