Spaces:
Sleeping
Sleeping
Upload 3 files
Browse files- aworld/logs/__init__.py +2 -0
- aworld/logs/log.py +174 -0
- aworld/logs/util.py +81 -0
aworld/logs/__init__.py
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
# coding: utf-8
|
2 |
+
# Copyright (c) 2025 inclusionAI.
|
aworld/logs/log.py
ADDED
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import sys
|
3 |
+
import os
|
4 |
+
from logging import StreamHandler
|
5 |
+
from abc import ABC
|
6 |
+
from logging import Logger, NOTSET, LogRecord, Filter, Formatter, Handler
|
7 |
+
from typing import Optional, Union
|
8 |
+
|
9 |
+
from aworld.trace.base import get_tracer_provider_silent, Tracer, AttributeValueType
|
10 |
+
|
11 |
+
TRACE_LOG_FORMAT = '%(asctime)s - [%(trace_id)s] - [%(span_id)s] - %(name)s - %(levelname)s - %(message)s'
|
12 |
+
SPECIAL_TRACE_LOG_FORMAT = '%(asctime)s - [trace_%(trace_id)s] - [%(span_id)s] - %(name)s - %(levelname)s - %(message)s'
|
13 |
+
|
14 |
+
|
15 |
+
class LoggerProvider(ABC):
|
16 |
+
"""A logger provider is a factory for loggers."""
|
17 |
+
|
18 |
+
|
19 |
+
_GLOBAL_LOG_PROVIDER: Optional[LoggerProvider] = None
|
20 |
+
|
21 |
+
|
22 |
+
def set_log_provider(provider: str = "otlp",
|
23 |
+
backend: str = "logfire",
|
24 |
+
base_url: str = None,
|
25 |
+
write_token: str = None,
|
26 |
+
**kwargs):
|
27 |
+
"""Set the global log provider."""
|
28 |
+
|
29 |
+
global _GLOBAL_LOG_PROVIDER
|
30 |
+
|
31 |
+
if provider == "otlp":
|
32 |
+
from .opentelemetry.otlp_log import OTLPLoggerProvider
|
33 |
+
_GLOBAL_LOG_PROVIDER = OTLPLoggerProvider(backend=backend,
|
34 |
+
base_url=base_url,
|
35 |
+
write_token=write_token,
|
36 |
+
**kwargs)
|
37 |
+
|
38 |
+
|
39 |
+
def get_log_provider() -> LoggerProvider:
|
40 |
+
"""
|
41 |
+
Get the global log provider.
|
42 |
+
"""
|
43 |
+
global _GLOBAL_LOG_PROVIDER
|
44 |
+
if _GLOBAL_LOG_PROVIDER is None:
|
45 |
+
raise ValueError("No log provider has been set.")
|
46 |
+
return _GLOBAL_LOG_PROVIDER
|
47 |
+
|
48 |
+
|
49 |
+
def instrument_logging(logger: Logger, level: Union[int, str] = NOTSET) -> None:
|
50 |
+
"""Instrument the logger."""
|
51 |
+
for handler in logger.root.handlers:
|
52 |
+
if not any(isinstance(filter, TraceLoggingFilter) for filter in handler.filters):
|
53 |
+
handler.setFormatter(Formatter(TRACE_LOG_FORMAT))
|
54 |
+
handler.addFilter(TraceLoggingFilter())
|
55 |
+
|
56 |
+
if not logger.handlers:
|
57 |
+
print("No handlers found, adding a StreamHandler. logger=", logger.name)
|
58 |
+
handler = StreamHandler()
|
59 |
+
handler.setFormatter(Formatter(SPECIAL_TRACE_LOG_FORMAT))
|
60 |
+
handler.addFilter(TraceLoggingFilter())
|
61 |
+
logger.addHandler(handler)
|
62 |
+
else:
|
63 |
+
for handler in logger.handlers:
|
64 |
+
if not any(isinstance(filter, TraceLoggingFilter) for filter in handler.filters):
|
65 |
+
handler.setFormatter(Formatter(SPECIAL_TRACE_LOG_FORMAT))
|
66 |
+
handler.addFilter(TraceLoggingFilter())
|
67 |
+
logger.propagate = False
|
68 |
+
logger.addHandler(TraceLogginHandler(level))
|
69 |
+
|
70 |
+
|
71 |
+
class TraceLoggingFilter(Filter):
|
72 |
+
"""
|
73 |
+
A filter that adds trace information to log records.
|
74 |
+
"""
|
75 |
+
|
76 |
+
def filter(self, record: LogRecord) -> bool:
|
77 |
+
"""
|
78 |
+
Add trace information to the log record.
|
79 |
+
"""
|
80 |
+
trace = get_tracer_provider_silent()
|
81 |
+
if trace:
|
82 |
+
span = trace.get_current_span()
|
83 |
+
record.trace_id = span.get_trace_id() if span else None
|
84 |
+
record.span_id = span.get_span_id() if span else None
|
85 |
+
return True
|
86 |
+
|
87 |
+
|
88 |
+
class TraceLogginHandler(Handler):
|
89 |
+
"""
|
90 |
+
A handler class which writes logging records, appropriately formatted,
|
91 |
+
to a stream. Note that this class does not close the stream, as
|
92 |
+
sys.stdout or sys.stderr may be used.
|
93 |
+
"""
|
94 |
+
@staticmethod
|
95 |
+
def strip_color(text: str) -> str:
|
96 |
+
"""Remove ANSI color codes from text"""
|
97 |
+
import re
|
98 |
+
return re.sub(r'\033\[[0-9;]*m', '', text)
|
99 |
+
|
100 |
+
def __init__(self,
|
101 |
+
level: Union[int, str] = NOTSET,
|
102 |
+
tracer_name: str = "aworld.log") -> None:
|
103 |
+
"""Initialize the handler."""
|
104 |
+
super().__init__(level=level)
|
105 |
+
self._tracer_name = tracer_name
|
106 |
+
self._tracer: Tracer = None
|
107 |
+
|
108 |
+
def emit(self, record: LogRecord) -> None:
|
109 |
+
"""Emit a record."""
|
110 |
+
trace = get_tracer_provider_silent()
|
111 |
+
if not trace or not trace.get_current_span() or not trace.get_current_span().is_recording():
|
112 |
+
return
|
113 |
+
|
114 |
+
if not self._tracer:
|
115 |
+
self._tracer = trace.get_tracer(name=self._tracer_name)
|
116 |
+
|
117 |
+
try:
|
118 |
+
f = sys._getframe()
|
119 |
+
while f:
|
120 |
+
if 'logging/__init__.py' in f.f_code.co_filename or \
|
121 |
+
f.f_code.co_filename.startswith(os.path.dirname(__file__)):
|
122 |
+
f = f.f_back
|
123 |
+
else:
|
124 |
+
break
|
125 |
+
|
126 |
+
origin_msg = record.msg
|
127 |
+
raw_msg = None
|
128 |
+
if f:
|
129 |
+
try:
|
130 |
+
import linecache
|
131 |
+
line = linecache.getline(f.f_code.co_filename, f.f_lineno)
|
132 |
+
if 'logger.' in line:
|
133 |
+
raw_msg = line.split('logger.', 1)[1].split(
|
134 |
+
'(', 1)[1].split(')', 1)[0].strip()
|
135 |
+
except:
|
136 |
+
pass
|
137 |
+
record.msg = self.strip_color(record.msg)
|
138 |
+
msg_template = raw_msg if raw_msg else record.msg
|
139 |
+
|
140 |
+
if len(msg_template) > 255:
|
141 |
+
msg_template = msg_template[:255] + '...'
|
142 |
+
|
143 |
+
attributes = {
|
144 |
+
'code.filepath': f.f_code.co_filename if f else record.pathname,
|
145 |
+
'code.lineno': f.f_lineno if f else record.lineno,
|
146 |
+
'code.function': f.f_code.co_name if f else record.funcName,
|
147 |
+
'log.level': record.levelname,
|
148 |
+
'log.logger': record.name,
|
149 |
+
'log.message': self.format(record),
|
150 |
+
}
|
151 |
+
record.msg = origin_msg
|
152 |
+
self._create_span(
|
153 |
+
span_name=msg_template,
|
154 |
+
attributes=attributes,
|
155 |
+
exc_info=record.exc_info,
|
156 |
+
)
|
157 |
+
except RecursionError: # See issue 36272
|
158 |
+
raise
|
159 |
+
except Exception:
|
160 |
+
self.handleError(record)
|
161 |
+
|
162 |
+
def _create_span(self,
|
163 |
+
span_name: str,
|
164 |
+
attributes: dict[str, AttributeValueType] = None,
|
165 |
+
exc_info: BaseException = None):
|
166 |
+
start_time = time.time_ns()
|
167 |
+
span = self._tracer.start_span(
|
168 |
+
name=span_name,
|
169 |
+
attributes=attributes,
|
170 |
+
start_time=start_time,
|
171 |
+
)
|
172 |
+
if exc_info:
|
173 |
+
span.record_exception(exception=exc_info, timestamp=start_time)
|
174 |
+
span.end()
|
aworld/logs/util.py
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# coding: utf-8
|
2 |
+
# Copyright (c) 2025 inclusionAI.
|
3 |
+
|
4 |
+
import logging
|
5 |
+
|
6 |
+
LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
7 |
+
logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
|
8 |
+
# common logger info
|
9 |
+
logger = logging.getLogger("common")
|
10 |
+
# for trace info
|
11 |
+
trace_logger = logging.getLogger("traced")
|
12 |
+
|
13 |
+
|
14 |
+
class Color:
|
15 |
+
black = '\033[30m'
|
16 |
+
red = '\033[31m'
|
17 |
+
green = '\033[32m'
|
18 |
+
orange = '\033[33m'
|
19 |
+
blue = '\033[34m'
|
20 |
+
purple = '\033[35m'
|
21 |
+
cyan = '\033[36m'
|
22 |
+
lightgrey = '\033[37m'
|
23 |
+
darkgrey = '\033[90m'
|
24 |
+
lightred = '\033[91m'
|
25 |
+
lightgreen = '\033[92m'
|
26 |
+
yellow = '\033[93m'
|
27 |
+
lightblue = '\033[94m'
|
28 |
+
pink = '\033[95m'
|
29 |
+
lightcyan = '\033[96m'
|
30 |
+
reset = '\033[0m'
|
31 |
+
bold = '\033[01m'
|
32 |
+
disable = '\033[02m'
|
33 |
+
underline = '\033[04m'
|
34 |
+
reverse = '\033[07m'
|
35 |
+
strikethrough = '\033[09m'
|
36 |
+
|
37 |
+
|
38 |
+
def color_log(value, color: str = Color.black, logger_: logging.Logger = logger, level: int = logging.INFO, hightlight_key=None):
|
39 |
+
""" Colored value or highlight key in log.
|
40 |
+
|
41 |
+
Args:
|
42 |
+
value:
|
43 |
+
color: Color
|
44 |
+
hightlight_key: Color segment key.
|
45 |
+
"""
|
46 |
+
if hightlight_key is None:
|
47 |
+
logger_.log(level, f"{color} {value} {Color.reset}")
|
48 |
+
else:
|
49 |
+
logger_.log(level, f"{color} {hightlight_key}: {Color.reset} {value}")
|
50 |
+
|
51 |
+
|
52 |
+
def aworld_log(logger, color: str = Color.black, level: int = logging.INFO):
|
53 |
+
"""Colored log style in the Aworld.
|
54 |
+
|
55 |
+
Args:
|
56 |
+
color: Default color set, different types of information can be set in different colors.
|
57 |
+
level: Log level.
|
58 |
+
"""
|
59 |
+
def_color = color
|
60 |
+
|
61 |
+
def decorator(value, color: str = None):
|
62 |
+
# Set color in the called.
|
63 |
+
if color:
|
64 |
+
color_log(value, color, logger, level)
|
65 |
+
else:
|
66 |
+
color_log(value, def_color, logger, level)
|
67 |
+
|
68 |
+
return decorator
|
69 |
+
|
70 |
+
|
71 |
+
def init_logger(logger: logging.Logger):
|
72 |
+
logger.debug = aworld_log(logger, color=Color.lightgrey, level=logging.DEBUG)
|
73 |
+
logger.info = aworld_log(logger, color=Color.black, level=logging.INFO)
|
74 |
+
logger.warning = aworld_log(logger, color=Color.orange, level=logging.WARNING)
|
75 |
+
logger.warn = logger.warning
|
76 |
+
logger.error = aworld_log(logger, color=Color.red, level=logging.ERROR)
|
77 |
+
logger.fatal = logger.error
|
78 |
+
|
79 |
+
|
80 |
+
init_logger(logger)
|
81 |
+
init_logger(trace_logger)
|