sibi-dst 2025.1.4__py3-none-any.whl → 2025.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sibi_dst/__init__.py +4 -1
- sibi_dst/df_helper/__init__.py +2 -2
- sibi_dst/df_helper/_artifact_updater_multi_wrapper.py +355 -163
- sibi_dst/df_helper/_df_helper.py +47 -30
- sibi_dst/df_helper/_parquet_artifact.py +57 -47
- sibi_dst/df_helper/_parquet_reader.py +9 -13
- sibi_dst/df_helper/backends/sqlalchemy/_db_connection.py +15 -11
- sibi_dst/df_helper/backends/sqlalchemy/_io_dask.py +23 -16
- sibi_dst/df_helper/backends/sqlalchemy/_load_from_db.py +17 -11
- sibi_dst/df_helper/backends/sqlalchemy/_sql_model_builder.py +1 -103
- sibi_dst/utils/__init__.py +3 -2
- sibi_dst/utils/base.py +97 -0
- sibi_dst/utils/clickhouse_writer.py +5 -4
- sibi_dst/utils/data_wrapper.py +69 -84
- sibi_dst/utils/date_utils.py +2 -1
- sibi_dst/utils/log_utils.py +309 -77
- sibi_dst/utils/manifest_manager.py +94 -373
- sibi_dst/utils/parquet_saver.py +98 -173
- sibi_dst/utils/storage_config.py +6 -0
- sibi_dst/utils/storage_manager.py +2 -1
- sibi_dst/utils/update_planner.py +72 -22
- {sibi_dst-2025.1.4.dist-info → sibi_dst-2025.1.5.dist-info}/METADATA +2 -1
- {sibi_dst-2025.1.4.dist-info → sibi_dst-2025.1.5.dist-info}/RECORD +24 -27
- sibi_dst/v3/__init__.py +0 -0
- sibi_dst/v3/backends/__init__.py +0 -0
- sibi_dst/v3/df_helper/__init__.py +0 -0
- sibi_dst/v3/df_helper/_df_helper.py +0 -91
- {sibi_dst-2025.1.4.dist-info → sibi_dst-2025.1.5.dist-info}/WHEEL +0 -0
sibi_dst/utils/log_utils.py
CHANGED
@@ -1,29 +1,27 @@
|
|
1
|
+
from __future__ import annotations
|
1
2
|
import logging
|
2
3
|
import os
|
3
4
|
import sys
|
4
5
|
import time
|
6
|
+
from logging import LoggerAdapter
|
5
7
|
from typing import Optional
|
8
|
+
from logging.handlers import RotatingFileHandler
|
9
|
+
|
10
|
+
# OpenTelemetry imports
|
11
|
+
from opentelemetry import trace
|
12
|
+
from opentelemetry._logs import set_logger_provider
|
13
|
+
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
|
14
|
+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
15
|
+
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
|
16
|
+
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
|
17
|
+
from opentelemetry.sdk.resources import Resource
|
18
|
+
from opentelemetry.sdk.trace import Tracer, TracerProvider
|
19
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
6
20
|
|
7
21
|
|
8
22
|
class Logger:
|
9
23
|
"""
|
10
|
-
Handles the creation
|
11
|
-
|
12
|
-
This class facilitates logging by creating and managing a logger instance with
|
13
|
-
customizable logging directory, name, and file. It ensures logs from a script
|
14
|
-
are stored in a well-defined directory and file, and provides various logging
|
15
|
-
methods for different log levels. The logger automatically formats and handles
|
16
|
-
log messages. Additionally, this class provides a class method to initialize a
|
17
|
-
logger with default behaviors.
|
18
|
-
|
19
|
-
:ivar log_dir: Path to the directory where log files are stored.
|
20
|
-
:type log_dir: str
|
21
|
-
:ivar logger_name: Name of the logger instance.
|
22
|
-
:type logger_name: str
|
23
|
-
:ivar log_file: Base name of the log file.
|
24
|
-
:type log_file: str
|
25
|
-
:ivar logger: The initialized logger instance used for logging messages.
|
26
|
-
:type logger: logging.Logger
|
24
|
+
Handles the creation and management of logging, with optional OpenTelemetry integration.
|
27
25
|
"""
|
28
26
|
|
29
27
|
DEBUG = logging.DEBUG
|
@@ -32,59 +30,114 @@ class Logger:
|
|
32
30
|
ERROR = logging.ERROR
|
33
31
|
CRITICAL = logging.CRITICAL
|
34
32
|
|
35
|
-
def __init__(
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
33
|
+
def __init__(
|
34
|
+
self,
|
35
|
+
log_dir: str,
|
36
|
+
logger_name: str,
|
37
|
+
log_file: str,
|
38
|
+
log_level: int = logging.DEBUG,
|
39
|
+
enable_otel: bool = False,
|
40
|
+
otel_service_name: Optional[str] = None,
|
41
|
+
otel_stream_name: Optional[str] = None,
|
42
|
+
otel_endpoint: str = "0.0.0.0:4317",
|
43
|
+
otel_insecure: bool = False,
|
44
|
+
):
|
44
45
|
self.log_dir = log_dir
|
45
46
|
self.logger_name = logger_name
|
46
47
|
self.log_file = log_file
|
47
48
|
self.log_level = log_level
|
48
|
-
self.
|
49
|
+
self.enable_otel = enable_otel
|
50
|
+
self.otel_service_name = otel_service_name or self.logger_name
|
51
|
+
self.otel_stream_name = otel_stream_name
|
52
|
+
self.otel_endpoint = otel_endpoint
|
53
|
+
self.otel_insecure = otel_insecure
|
54
|
+
self.logger_provider: Optional[LoggerProvider] = None
|
55
|
+
self.tracer_provider: Optional[TracerProvider] = None
|
56
|
+
self.tracer: Optional[Tracer] = None
|
57
|
+
|
58
|
+
# Internal logger for configuration vs. public logger for use
|
59
|
+
self._core_logger: Optional[logging.Logger] = logging.getLogger(self.logger_name)
|
60
|
+
self.logger: Optional[logging.Logger | LoggerAdapter] = self._core_logger
|
49
61
|
|
50
62
|
self._setup()
|
51
63
|
|
52
64
|
def _setup(self):
|
53
|
-
"""Set up the logger
|
54
|
-
#
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
calling_script = os.path.splitext(os.path.basename(sys.argv[0]))[0]
|
65
|
+
"""Set up the logger and then wrap it in an adapter if needed."""
|
66
|
+
# 1. Create and configure the actual logger instance
|
67
|
+
self._core_logger = logging.getLogger(self.logger_name)
|
68
|
+
self._core_logger.setLevel(self.log_level)
|
69
|
+
self._core_logger.propagate = False
|
59
70
|
|
60
|
-
#
|
61
|
-
|
71
|
+
# 2. Add all handlers to the actual logger instance
|
72
|
+
self._setup_standard_handlers()
|
73
|
+
if self.enable_otel:
|
74
|
+
self._setup_otel_handler()
|
62
75
|
|
63
|
-
#
|
64
|
-
if
|
65
|
-
|
76
|
+
# 3. Create the final, public-facing logger object
|
77
|
+
if self.enable_otel and self.otel_stream_name:
|
78
|
+
# If stream name is used, wrap the configured logger in an adapter
|
79
|
+
attributes = {"attributes": {
|
80
|
+
"log_stream": self.otel_stream_name,
|
81
|
+
"log_service_name": self.otel_service_name,
|
82
|
+
"logger_name": self.logger_name,
|
83
|
+
}
|
84
|
+
}
|
85
|
+
self.logger = LoggerAdapter(self._core_logger, extra=attributes)
|
86
|
+
else:
|
87
|
+
# Otherwise, just use the logger directly
|
88
|
+
self.logger = self._core_logger
|
66
89
|
|
67
|
-
|
68
|
-
|
69
|
-
|
90
|
+
def _setup_standard_handlers(self):
|
91
|
+
"""Sets up the file and console logging handlers."""
|
92
|
+
os.makedirs(self.log_dir, exist_ok=True)
|
93
|
+
calling_script = os.path.splitext(os.path.basename(sys.argv[0]))[0]
|
94
|
+
log_file_path = os.path.join(
|
95
|
+
self.log_dir, f"{self.log_file}_{calling_script}.log"
|
96
|
+
)
|
70
97
|
|
71
|
-
# Create a formatter
|
72
98
|
formatter = logging.Formatter(
|
73
99
|
'[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s',
|
74
|
-
datefmt='%Y-%m-%d %H:%M:%S'
|
100
|
+
datefmt='%Y-%m-%d %H:%M:%S',
|
75
101
|
)
|
102
|
+
#formatter.converter = time.localtime
|
103
|
+
formatter.converter = time.gmtime # Use GMT for consistency in logs
|
76
104
|
|
77
|
-
|
78
|
-
|
79
|
-
# Create a file handler
|
80
|
-
file_handler = logging.FileHandler(log_file_path, delay=True)
|
105
|
+
#file_handler = logging.FileHandler(log_file_path, delay=True)
|
106
|
+
file_handler = RotatingFileHandler(log_file_path, maxBytes=5*1024*1024, backupCount=5, delay=True)
|
81
107
|
file_handler.setFormatter(formatter)
|
82
|
-
self.
|
108
|
+
self._core_logger.addHandler(file_handler)
|
83
109
|
|
84
|
-
|
85
|
-
console_handler = logging.StreamHandler()
|
110
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
86
111
|
console_handler.setFormatter(formatter)
|
87
|
-
self.
|
112
|
+
self._core_logger.addHandler(console_handler)
|
113
|
+
|
114
|
+
def _setup_otel_handler(self):
|
115
|
+
"""Sets up the OpenTelemetry logging handler."""
|
116
|
+
resource = Resource.create({"service.name": self.otel_stream_name or self.otel_service_name})
|
117
|
+
self.logger_provider = LoggerProvider(resource=resource)
|
118
|
+
set_logger_provider(self.logger_provider)
|
119
|
+
self.tracer_provider = TracerProvider(resource=resource)
|
120
|
+
trace.set_tracer_provider(self.tracer_provider)
|
121
|
+
|
122
|
+
exporter = OTLPLogExporter(
|
123
|
+
endpoint=self.otel_endpoint, insecure=self.otel_insecure
|
124
|
+
)
|
125
|
+
log_processor = BatchLogRecordProcessor(exporter)
|
126
|
+
self.logger_provider.add_log_record_processor(log_processor)
|
127
|
+
|
128
|
+
span_exporter = OTLPSpanExporter(
|
129
|
+
endpoint=self.otel_endpoint, insecure=self.otel_insecure
|
130
|
+
)
|
131
|
+
span_processor = BatchSpanProcessor(span_exporter)
|
132
|
+
self.tracer_provider.add_span_processor(span_processor)
|
133
|
+
self.tracer = trace.get_tracer(
|
134
|
+
self.logger_name, tracer_provider=self.tracer_provider
|
135
|
+
)
|
136
|
+
otel_handler = LoggingHandler(
|
137
|
+
level=logging.NOTSET, logger_provider=self.logger_provider
|
138
|
+
)
|
139
|
+
self._core_logger.addHandler(otel_handler)
|
140
|
+
self._core_logger.info("OpenTelemetry logging and tracing enabled and attached.")
|
88
141
|
|
89
142
|
@classmethod
|
90
143
|
def default_logger(
|
@@ -92,49 +145,228 @@ class Logger:
|
|
92
145
|
log_dir: str = './logs/',
|
93
146
|
logger_name: Optional[str] = None,
|
94
147
|
log_file: Optional[str] = None,
|
95
|
-
log_level: int = logging.INFO
|
148
|
+
log_level: int = logging.INFO,
|
149
|
+
enable_otel: bool = False,
|
150
|
+
otel_service_name: Optional[str] = None,
|
151
|
+
otel_stream_name: Optional[str] = None,
|
152
|
+
otel_endpoint: str = "0.0.0.0:4317",
|
153
|
+
otel_insecure: bool = False,
|
96
154
|
) -> 'Logger':
|
97
|
-
|
98
|
-
|
155
|
+
try:
|
156
|
+
frame = sys._getframe(1)
|
157
|
+
caller_name = frame.f_globals.get('__name__', 'default_logger')
|
158
|
+
except (AttributeError, ValueError):
|
159
|
+
caller_name = 'default_logger'
|
99
160
|
|
100
|
-
|
101
|
-
:param logger_name: Name of the logger (defaults to __name__).
|
102
|
-
:param log_file: Name of the log file (defaults to logger_name).
|
103
|
-
:param log_level: Logging level (defaults to INFO).
|
104
|
-
:return: Instance of Logger.
|
105
|
-
"""
|
106
|
-
logger_name = logger_name or __name__
|
161
|
+
logger_name = logger_name or caller_name
|
107
162
|
log_file = log_file or logger_name
|
108
|
-
return cls(log_dir=log_dir, logger_name=logger_name, log_file=log_file, log_level=log_level)
|
109
163
|
|
110
|
-
|
111
|
-
|
112
|
-
|
164
|
+
return cls(
|
165
|
+
log_dir=log_dir,
|
166
|
+
logger_name=logger_name,
|
167
|
+
log_file=log_file,
|
168
|
+
log_level=log_level,
|
169
|
+
enable_otel=enable_otel,
|
170
|
+
otel_service_name=otel_service_name,
|
171
|
+
otel_stream_name=otel_stream_name,
|
172
|
+
otel_endpoint=otel_endpoint,
|
173
|
+
otel_insecure=otel_insecure,
|
174
|
+
)
|
113
175
|
|
114
|
-
|
115
|
-
"""
|
116
|
-
self.
|
176
|
+
def shutdown(self):
|
177
|
+
"""Gracefully shuts down the logger and the OpenTelemetry provider."""
|
178
|
+
if self.enable_otel and self.logger_provider:
|
179
|
+
self.logger.info("Shutting down OpenTelemetry logger provider...")
|
180
|
+
if self.otel_stream_name:
|
181
|
+
self._core_logger.info(f"OpenObserve stream configured as: '{self.otel_stream_name}'")
|
182
|
+
self.logger_provider.shutdown()
|
183
|
+
print("Logger provider shut down.")
|
184
|
+
logging.shutdown()
|
185
|
+
|
186
|
+
def set_level(self, level: int):
|
187
|
+
"""Set the logging level for the logger."""
|
188
|
+
self._core_logger.setLevel(level)
|
117
189
|
|
118
190
|
def debug(self, msg: str, *args, **kwargs):
|
119
|
-
"""Log a debug message."""
|
120
191
|
self.logger.debug(msg, *args, **kwargs)
|
121
192
|
|
122
193
|
def info(self, msg: str, *args, **kwargs):
|
123
|
-
"""Log an info message."""
|
124
194
|
self.logger.info(msg, *args, **kwargs)
|
125
195
|
|
126
196
|
def warning(self, msg: str, *args, **kwargs):
|
127
|
-
"""Log a warning message."""
|
128
197
|
self.logger.warning(msg, *args, **kwargs)
|
129
198
|
|
130
199
|
def error(self, msg: str, *args, **kwargs):
|
131
|
-
"""
|
132
|
-
Log an error message.
|
133
|
-
|
134
|
-
To log exception information, use the `exc_info=True` keyword argument.
|
135
|
-
"""
|
136
200
|
self.logger.error(msg, *args, **kwargs)
|
137
201
|
|
138
202
|
def critical(self, msg: str, *args, **kwargs):
|
139
|
-
"""Log a critical message."""
|
140
203
|
self.logger.critical(msg, *args, **kwargs)
|
204
|
+
|
205
|
+
def start_span(self, name: str, attributes: Optional[dict] = None):
|
206
|
+
"""
|
207
|
+
Starts a span using the configured tracer.
|
208
|
+
Usage:
|
209
|
+
with logger.start_span("my-task") as span:
|
210
|
+
...
|
211
|
+
"""
|
212
|
+
if not self.enable_otel or not self.tracer:
|
213
|
+
self.warning("Tracing is disabled or not initialized. Cannot start span.")
|
214
|
+
# return dummy context manager
|
215
|
+
from contextlib import nullcontext
|
216
|
+
return nullcontext()
|
217
|
+
|
218
|
+
return self.tracer.start_as_current_span(name)
|
219
|
+
|
220
|
+
# Use this decorator to trace a function
|
221
|
+
def trace_function(self, span_name: Optional[str] = None):
|
222
|
+
def decorator(func):
|
223
|
+
def wrapper(*args, **kwargs):
|
224
|
+
name = span_name or func.__name__
|
225
|
+
with self.start_span(name):
|
226
|
+
return func(*args, **kwargs)
|
227
|
+
|
228
|
+
return wrapper
|
229
|
+
|
230
|
+
return decorator
|
231
|
+
|
232
|
+
|
233
|
+
# import logging
|
234
|
+
# import os
|
235
|
+
# import sys
|
236
|
+
# import time
|
237
|
+
# from typing import Optional
|
238
|
+
#
|
239
|
+
#
|
240
|
+
# class Logger:
|
241
|
+
# """
|
242
|
+
# Handles the creation, setup, and management of logging functionalities.
|
243
|
+
#
|
244
|
+
# This class facilitates logging by creating and managing a logger instance with
|
245
|
+
# customizable logging directory, name, and file. It ensures logs from a script
|
246
|
+
# are stored in a well-defined directory and file, and provides various logging
|
247
|
+
# methods for different log levels. The logger automatically formats and handles
|
248
|
+
# log messages. Additionally, this class provides a class method to initialize a
|
249
|
+
# logger with default behaviors.
|
250
|
+
#
|
251
|
+
# :ivar log_dir: Path to the directory where log files are stored.
|
252
|
+
# :type log_dir: str
|
253
|
+
# :ivar logger_name: Name of the logger instance.
|
254
|
+
# :type logger_name: str
|
255
|
+
# :ivar log_file: Base name of the log file.
|
256
|
+
# :type log_file: str
|
257
|
+
# :ivar logger: The initialized logger instance used for logging messages.
|
258
|
+
# :type logger: logging.Logger
|
259
|
+
# """
|
260
|
+
#
|
261
|
+
# DEBUG = logging.DEBUG
|
262
|
+
# INFO = logging.INFO
|
263
|
+
# WARNING = logging.WARNING
|
264
|
+
# ERROR = logging.ERROR
|
265
|
+
# CRITICAL = logging.CRITICAL
|
266
|
+
#
|
267
|
+
# def __init__(self, log_dir: str, logger_name: str, log_file: str, log_level: int = logging.DEBUG):
|
268
|
+
# """
|
269
|
+
# Initialize the Logger instance.
|
270
|
+
#
|
271
|
+
# :param log_dir: Directory where logs are stored.
|
272
|
+
# :param logger_name: Name of the logger instance.
|
273
|
+
# :param log_file: Base name of the log file.
|
274
|
+
# :param log_level: Logging level (defaults to DEBUG).
|
275
|
+
# """
|
276
|
+
# self.log_dir = log_dir
|
277
|
+
# self.logger_name = logger_name
|
278
|
+
# self.log_file = log_file
|
279
|
+
# self.log_level = log_level
|
280
|
+
# self.logger = None
|
281
|
+
#
|
282
|
+
# self._setup()
|
283
|
+
#
|
284
|
+
# def _setup(self):
|
285
|
+
# """Set up the logger with file and console handlers."""
|
286
|
+
# # Ensure the log directory exists
|
287
|
+
# os.makedirs(self.log_dir, exist_ok=True)
|
288
|
+
#
|
289
|
+
# # Get the name of the calling script
|
290
|
+
# calling_script = os.path.splitext(os.path.basename(sys.argv[0]))[0]
|
291
|
+
#
|
292
|
+
# # Create a log file path
|
293
|
+
# log_file_path = os.path.join(self.log_dir, f"{self.log_file}_{calling_script}.log")
|
294
|
+
#
|
295
|
+
# # Delete the existing log file if it exists
|
296
|
+
# if os.path.exists(log_file_path):
|
297
|
+
# os.remove(log_file_path)
|
298
|
+
#
|
299
|
+
# # Create a logger
|
300
|
+
# self.logger = logging.getLogger(self.logger_name)
|
301
|
+
# self.logger.setLevel(self.log_level)
|
302
|
+
#
|
303
|
+
# # Create a formatter
|
304
|
+
# formatter = logging.Formatter(
|
305
|
+
# '[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s',
|
306
|
+
# datefmt='%Y-%m-%d %H:%M:%S'
|
307
|
+
# )
|
308
|
+
#
|
309
|
+
# formatter.converter = time.localtime # << Set local time explicitly
|
310
|
+
#
|
311
|
+
# # Create a file handler
|
312
|
+
# file_handler = logging.FileHandler(log_file_path, delay=True)
|
313
|
+
# file_handler.setFormatter(formatter)
|
314
|
+
# self.logger.addHandler(file_handler)
|
315
|
+
#
|
316
|
+
# # Create a console handler (optional)
|
317
|
+
# console_handler = logging.StreamHandler()
|
318
|
+
# console_handler.setFormatter(formatter)
|
319
|
+
# self.logger.addHandler(console_handler)
|
320
|
+
#
|
321
|
+
# @classmethod
|
322
|
+
# def default_logger(
|
323
|
+
# cls,
|
324
|
+
# log_dir: str = './logs/',
|
325
|
+
# logger_name: Optional[str] = None,
|
326
|
+
# log_file: Optional[str] = None,
|
327
|
+
# log_level: int = logging.INFO
|
328
|
+
# ) -> 'Logger':
|
329
|
+
# """
|
330
|
+
# Class-level method to create a default logger with generic parameters.
|
331
|
+
#
|
332
|
+
# :param log_dir: Directory where logs are stored (defaults to './logs/').
|
333
|
+
# :param logger_name: Name of the logger (defaults to __name__).
|
334
|
+
# :param log_file: Name of the log file (defaults to logger_name).
|
335
|
+
# :param log_level: Logging level (defaults to INFO).
|
336
|
+
# :return: Instance of Logger.
|
337
|
+
# """
|
338
|
+
# logger_name = logger_name or __name__
|
339
|
+
# log_file = log_file or logger_name
|
340
|
+
# return cls(log_dir=log_dir, logger_name=logger_name, log_file=log_file, log_level=log_level)
|
341
|
+
#
|
342
|
+
# def set_level(self, level: int):
|
343
|
+
# """
|
344
|
+
# Set the logging level for the logger.
|
345
|
+
#
|
346
|
+
# :param level: Logging level (e.g., logging.DEBUG, logging.INFO).
|
347
|
+
# """
|
348
|
+
# self.logger.setLevel(level)
|
349
|
+
#
|
350
|
+
# def debug(self, msg: str, *args, **kwargs):
|
351
|
+
# """Log a debug message."""
|
352
|
+
# self.logger.debug(msg, *args, **kwargs)
|
353
|
+
#
|
354
|
+
# def info(self, msg: str, *args, **kwargs):
|
355
|
+
# """Log an info message."""
|
356
|
+
# self.logger.info(msg, *args, **kwargs)
|
357
|
+
#
|
358
|
+
# def warning(self, msg: str, *args, **kwargs):
|
359
|
+
# """Log a warning message."""
|
360
|
+
# self.logger.warning(msg, *args, **kwargs)
|
361
|
+
#
|
362
|
+
# def error(self, msg: str, *args, **kwargs):
|
363
|
+
# """
|
364
|
+
# Log an error message.
|
365
|
+
#
|
366
|
+
# To log exception information, use the `exc_info=True` keyword argument.
|
367
|
+
# """
|
368
|
+
# self.logger.error(msg, *args, **kwargs)
|
369
|
+
#
|
370
|
+
# def critical(self, msg: str, *args, **kwargs):
|
371
|
+
# """Log a critical message."""
|
372
|
+
# self.logger.critical(msg, *args, **kwargs)
|