sibi-dst 2025.1.4__py3-none-any.whl → 2025.1.6__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.
@@ -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, setup, and management of logging functionalities.
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__(self, log_dir: str, logger_name: str, log_file: str, log_level: int = logging.DEBUG):
36
- """
37
- Initialize the Logger instance.
38
-
39
- :param log_dir: Directory where logs are stored.
40
- :param logger_name: Name of the logger instance.
41
- :param log_file: Base name of the log file.
42
- :param log_level: Logging level (defaults to DEBUG).
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.logger = None
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 with file and console handlers."""
54
- # Ensure the log directory exists
55
- os.makedirs(self.log_dir, exist_ok=True)
56
-
57
- # Get the name of the calling script
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
- # Create a log file path
61
- log_file_path = os.path.join(self.log_dir, f"{self.log_file}_{calling_script}.log")
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
- # Delete the existing log file if it exists
64
- if os.path.exists(log_file_path):
65
- os.remove(log_file_path)
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
- # Create a logger
68
- self.logger = logging.getLogger(self.logger_name)
69
- self.logger.setLevel(self.log_level)
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
- formatter.converter = time.localtime # << Set local time explicitly
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.logger.addHandler(file_handler)
108
+ self._core_logger.addHandler(file_handler)
83
109
 
84
- # Create a console handler (optional)
85
- console_handler = logging.StreamHandler()
110
+ console_handler = logging.StreamHandler(sys.stdout)
86
111
  console_handler.setFormatter(formatter)
87
- self.logger.addHandler(console_handler)
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
- Class-level method to create a default logger with generic parameters.
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
- :param log_dir: Directory where logs are stored (defaults to './logs/').
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
- def set_level(self, level: int):
111
- """
112
- Set the logging level for the logger.
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
- :param level: Logging level (e.g., logging.DEBUG, logging.INFO).
115
- """
116
- self.logger.setLevel(level)
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)