chutils 2.2.1__tar.gz → 2.3.0__tar.gz

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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chutils
3
- Version: 2.2.1
3
+ Version: 2.3.0
4
4
  Summary: Набор простых и удобных утилит для Python, который избавляет от рутины при работе с конфигурацией и логированием в новых проектах.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -49,7 +49,8 @@ Description-Content-Type: text/markdown
49
49
  ## Ключевые возможности
50
50
 
51
51
  - **✨ Ноль конфигурации:** Библиотека **автоматически** находит корень вашего проекта и файл `config.yml` или
52
- `config.ini`. Если файл не найден, используются безопасные настройки по умолчанию (например, логирование только в консоль).
52
+ `config.ini`. Если файл не найден, используются безопасные настройки по умолчанию (например, логирование только в
53
+ консоль).
53
54
  - **⚙️ Гибкая конфигурация:** Поддержка `YAML` и `INI` форматов. Простые функции для получения типизированных данных.
54
55
  - **✍️ Продвинутый логгер:** Функция `setup_logger()` "из коробки" настраивает логирование в консоль и в ротируемые
55
56
  файлы. Возвращает кастомный логгер с дополнительными уровнями отладки (`devdebug`, `mediumdebug`).
@@ -86,7 +87,8 @@ pip install -e .
86
87
 
87
88
  ### 1. Работа с конфигурацией
88
89
 
89
- 1. (Опционально) Создайте файл `config.yml` в корне вашего проекта. Если этого не сделать, библиотека будет использовать настройки по умолчанию:
90
+ 1. (Опционально) Создайте файл `config.yml` в корне вашего проекта. Если этого не сделать, библиотека будет использовать
91
+ настройки по умолчанию:
90
92
 
91
93
  ```yaml
92
94
  # config.yml
@@ -115,8 +117,9 @@ pip install -e .
115
117
  Вы можете создать локальный файл конфигурации (например, `config.local.yml` или `config.local.ini`) рядом с основным
116
118
  файлом (`config.yml` или `config.ini`). Значения из локального файла будут **переопределять** соответствующие
117
119
  значения из основного файла. Это удобно для:
118
- - Хранения чувствительных данных, которые не должны попадать в систему контроля версий (добавьте `config.local.yml` в `.gitignore`).
119
- - Переопределения настроек для локальной разработки без изменения основного файла.
120
+ - Хранения чувствительных данных, которые не должны попадать в систему контроля версий (добавьте `config.local.yml`
121
+ в `.gitignore`).
122
+ - Переопределения настроек для локальной разработки без изменения основного файла.
120
123
 
121
124
  Пример:
122
125
  Если `config.yml` содержит:
@@ -188,7 +191,8 @@ pip install -e .
188
191
 
189
192
  #### Создание нескольких логгеров
190
193
 
191
- Вы можете создавать разные логгеры для разных частей вашего приложения, передавая уникальное имя в `setup_logger`. Это
194
+ Вы можете создавать разные логгеры для разных частей вашего приложения, передавая уникальное имя в `setup_logger`.
195
+ Это
192
196
  помогает фильтровать и разделять логи.
193
197
 
194
198
  ```python
@@ -206,12 +210,46 @@ pip install -e .
206
210
  В лог-файлах вы увидите сообщения от соответствующих логгеров.
207
211
  Более подробный пример можно найти в [`/examples/05_different_log_levels.py`](./examples/05_different_log_levels.py).
208
212
 
213
+ #### Конфигурация нескольких логгеров через файл
214
+
215
+ Вы можете централизованно управлять настройками разных логгеров, используя параметр `config_section_name`.
216
+
217
+ 1. **Добавьте секции в `config.yml`**:
218
+ Секция `[Logging]` используется для настроек по умолчанию. Остальные секции можно использовать для специфичных
219
+ логгеров.
220
+ ```yaml
221
+ # config.yml
222
+ Logging:
223
+ log_level: INFO
224
+ rotation_type: time
225
+ compress: true
226
+
227
+ AuditLogger:
228
+ log_level: DEBUG
229
+ log_file_name: "audit.log"
230
+ ```
231
+
232
+ 2. **Используйте `config_section_name` в коде**:
233
+ ```python
234
+ # main.py
235
+ from chutils import setup_logger
236
+
237
+ # Этот логгер возьмет настройки из секции [Logging]
238
+ main_logger = setup_logger("main")
239
+ main_logger.info("Сообщение от основного логгера.")
240
+
241
+ # А этот логгер - из секции [AuditLogger], которая переопределит настройки из [Logging]
242
+ audit_logger = setup_logger("audit", config_section_name="AuditLogger")
243
+ audit_logger.debug("Детальное сообщение для аудита.")
244
+ ```
245
+
209
246
  ### 3. Управление секретами
210
247
 
211
248
  `SecretManager` ищет секреты в следующем порядке:
212
- 1. **Системное хранилище (`keyring`)**: Наиболее безопасный способ.
213
- 2. **Файл `.env`**: Если секрет не найден в `keyring`, менеджер будет искать его в файле `.env` в корне вашего проекта.
214
- 3. **Переменные окружения**: Если секрета нет и там, будет произведен поиск в переменных окружения ОС.
249
+
250
+ 1. **Системное хранилище (`keyring`)**: Наиболее безопасный способ.
251
+ 2. **Файл `.env`**: Если секрет не найден в `keyring`, менеджер будет искать его в файле `.env` в корне вашего проекта.
252
+ 3. **Переменные окружения**: Если секрета нет и там, будет произведен поиск в переменных окружения ОС.
215
253
 
216
254
  #### Способ 1: Keyring (рекомендуемый)
217
255
 
@@ -345,7 +383,8 @@ pip install -e .
345
383
 
346
384
  - `setup_logger(name='app_logger', log_level_str='')`: Настраивает и возвращает экземпляр `ChutilsLogger`.
347
385
  - `logger.mediumdebug("message")`: Логирование с уровнем 15. Промежуточный уровень между `DEBUG` и `INFO`.
348
- - `logger.devdebug("message")`: Логирование с уровнем 9. Самый подробный уровень для глубокой отладки (например, для вывода дампов переменных).
386
+ - `logger.devdebug("message")`: Логирование с уровнем 9. Самый подробный уровень для глубокой отладки (например, для
387
+ вывода дампов переменных).
349
388
 
350
389
  ### Управление секретами (`chutils.secret_manager`)
351
390
 
@@ -26,7 +26,8 @@
26
26
  ## Ключевые возможности
27
27
 
28
28
  - **✨ Ноль конфигурации:** Библиотека **автоматически** находит корень вашего проекта и файл `config.yml` или
29
- `config.ini`. Если файл не найден, используются безопасные настройки по умолчанию (например, логирование только в консоль).
29
+ `config.ini`. Если файл не найден, используются безопасные настройки по умолчанию (например, логирование только в
30
+ консоль).
30
31
  - **⚙️ Гибкая конфигурация:** Поддержка `YAML` и `INI` форматов. Простые функции для получения типизированных данных.
31
32
  - **✍️ Продвинутый логгер:** Функция `setup_logger()` "из коробки" настраивает логирование в консоль и в ротируемые
32
33
  файлы. Возвращает кастомный логгер с дополнительными уровнями отладки (`devdebug`, `mediumdebug`).
@@ -63,7 +64,8 @@ pip install -e .
63
64
 
64
65
  ### 1. Работа с конфигурацией
65
66
 
66
- 1. (Опционально) Создайте файл `config.yml` в корне вашего проекта. Если этого не сделать, библиотека будет использовать настройки по умолчанию:
67
+ 1. (Опционально) Создайте файл `config.yml` в корне вашего проекта. Если этого не сделать, библиотека будет использовать
68
+ настройки по умолчанию:
67
69
 
68
70
  ```yaml
69
71
  # config.yml
@@ -92,8 +94,9 @@ pip install -e .
92
94
  Вы можете создать локальный файл конфигурации (например, `config.local.yml` или `config.local.ini`) рядом с основным
93
95
  файлом (`config.yml` или `config.ini`). Значения из локального файла будут **переопределять** соответствующие
94
96
  значения из основного файла. Это удобно для:
95
- - Хранения чувствительных данных, которые не должны попадать в систему контроля версий (добавьте `config.local.yml` в `.gitignore`).
96
- - Переопределения настроек для локальной разработки без изменения основного файла.
97
+ - Хранения чувствительных данных, которые не должны попадать в систему контроля версий (добавьте `config.local.yml`
98
+ в `.gitignore`).
99
+ - Переопределения настроек для локальной разработки без изменения основного файла.
97
100
 
98
101
  Пример:
99
102
  Если `config.yml` содержит:
@@ -165,7 +168,8 @@ pip install -e .
165
168
 
166
169
  #### Создание нескольких логгеров
167
170
 
168
- Вы можете создавать разные логгеры для разных частей вашего приложения, передавая уникальное имя в `setup_logger`. Это
171
+ Вы можете создавать разные логгеры для разных частей вашего приложения, передавая уникальное имя в `setup_logger`.
172
+ Это
169
173
  помогает фильтровать и разделять логи.
170
174
 
171
175
  ```python
@@ -183,12 +187,46 @@ pip install -e .
183
187
  В лог-файлах вы увидите сообщения от соответствующих логгеров.
184
188
  Более подробный пример можно найти в [`/examples/05_different_log_levels.py`](./examples/05_different_log_levels.py).
185
189
 
190
+ #### Конфигурация нескольких логгеров через файл
191
+
192
+ Вы можете централизованно управлять настройками разных логгеров, используя параметр `config_section_name`.
193
+
194
+ 1. **Добавьте секции в `config.yml`**:
195
+ Секция `[Logging]` используется для настроек по умолчанию. Остальные секции можно использовать для специфичных
196
+ логгеров.
197
+ ```yaml
198
+ # config.yml
199
+ Logging:
200
+ log_level: INFO
201
+ rotation_type: time
202
+ compress: true
203
+
204
+ AuditLogger:
205
+ log_level: DEBUG
206
+ log_file_name: "audit.log"
207
+ ```
208
+
209
+ 2. **Используйте `config_section_name` в коде**:
210
+ ```python
211
+ # main.py
212
+ from chutils import setup_logger
213
+
214
+ # Этот логгер возьмет настройки из секции [Logging]
215
+ main_logger = setup_logger("main")
216
+ main_logger.info("Сообщение от основного логгера.")
217
+
218
+ # А этот логгер - из секции [AuditLogger], которая переопределит настройки из [Logging]
219
+ audit_logger = setup_logger("audit", config_section_name="AuditLogger")
220
+ audit_logger.debug("Детальное сообщение для аудита.")
221
+ ```
222
+
186
223
  ### 3. Управление секретами
187
224
 
188
225
  `SecretManager` ищет секреты в следующем порядке:
189
- 1. **Системное хранилище (`keyring`)**: Наиболее безопасный способ.
190
- 2. **Файл `.env`**: Если секрет не найден в `keyring`, менеджер будет искать его в файле `.env` в корне вашего проекта.
191
- 3. **Переменные окружения**: Если секрета нет и там, будет произведен поиск в переменных окружения ОС.
226
+
227
+ 1. **Системное хранилище (`keyring`)**: Наиболее безопасный способ.
228
+ 2. **Файл `.env`**: Если секрет не найден в `keyring`, менеджер будет искать его в файле `.env` в корне вашего проекта.
229
+ 3. **Переменные окружения**: Если секрета нет и там, будет произведен поиск в переменных окружения ОС.
192
230
 
193
231
  #### Способ 1: Keyring (рекомендуемый)
194
232
 
@@ -322,7 +360,8 @@ pip install -e .
322
360
 
323
361
  - `setup_logger(name='app_logger', log_level_str='')`: Настраивает и возвращает экземпляр `ChutilsLogger`.
324
362
  - `logger.mediumdebug("message")`: Логирование с уровнем 15. Промежуточный уровень между `DEBUG` и `INFO`.
325
- - `logger.devdebug("message")`: Логирование с уровнем 9. Самый подробный уровень для глубокой отладки (например, для вывода дампов переменных).
363
+ - `logger.devdebug("message")`: Логирование с уровнем 9. Самый подробный уровень для глубокой отладки (например, для
364
+ вывода дампов переменных).
326
365
 
327
366
  ### Управление секретами (`chutils.secret_manager`)
328
367
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "chutils"
3
- version = "2.2.1"
3
+ version = "2.3.0"
4
4
  description = "Набор простых и удобных утилит для Python, который избавляет от рутины при работе с конфигурацией и логированием в новых проектах."
5
5
  authors = ["Chu4hel <sergeiivanov636@gmail.com>"]
6
6
  license = "MIT"
@@ -8,6 +8,7 @@ readme = "README.md"
8
8
  packages = [{ include = "chutils", from = "src" }]
9
9
  exclude = [
10
10
  "tests/",
11
+ "site/",
11
12
  "examples/",
12
13
  "docs/",
13
14
  ".github/",
@@ -181,18 +181,56 @@ def _load_yaml(path: str) -> Dict:
181
181
  return {}
182
182
 
183
183
 
184
+ def _nest_ini_dict(flat_dict: Dict[str, Dict[str, Any]]) -> Dict:
185
+ """
186
+ Преобразует плоский словарь INI-секций (с точками в именах секций)
187
+ во вложенную структуру словарей.
188
+ Например: {'Logging.default': {'key': 'value'}} -> {'Logging': {'default': {'key': 'value'}}}
189
+ """
190
+ nested_dict = {}
191
+ for section_key, section_values in flat_dict.items():
192
+ current_level = nested_dict
193
+ parts = section_key.split('.')
194
+ for i, part in enumerate(parts):
195
+ if i == len(parts) - 1: # Последняя часть - это название секции
196
+ current_level[part] = section_values
197
+ else:
198
+ current_level = current_level.setdefault(part, {})
199
+ return nested_dict
200
+
201
+
184
202
  def _load_ini(path: str) -> Dict:
185
203
  """Загружает и парсит INI-файл."""
186
204
  try:
187
205
  with open(path, 'r', encoding='utf-8') as f:
188
206
  parser = configparser.ConfigParser()
189
207
  parser.read_string(f.read())
190
- return {s: dict(parser.items(s)) for s in parser.sections()}
208
+ flat_ini_config = {s: dict(parser.items(s)) for s in parser.sections()}
209
+ # Преобразуем плоскую структуру вложенных секций в иерархическую
210
+ return _nest_ini_dict(flat_ini_config)
191
211
  except (configparser.Error, FileNotFoundError) as e:
192
212
  logger.critical("Ошибка чтения INI файла конфигурации %s: %s", path, e)
193
213
  return {}
194
214
 
195
215
 
216
+ def _nest_ini_dict(flat_dict: Dict[str, Dict[str, Any]]) -> Dict:
217
+ """
218
+ Преобразует плоский словарь INI-секций (с точками в именах секций)
219
+ во вложенную структуру словарей.
220
+ Например: {'Logging.default': {'key': 'value'}} -> {'Logging': {'default': {'key': 'value'}}}
221
+ """
222
+ nested_dict = {}
223
+ for section_key, section_values in flat_dict.items():
224
+ current_level = nested_dict
225
+ parts = section_key.split('.')
226
+ for i, part in enumerate(parts):
227
+ if i == len(parts) - 1: # Последняя часть - это название секции
228
+ current_level[part] = section_values
229
+ else:
230
+ current_level = current_level.setdefault(part, {})
231
+ return nested_dict
232
+
233
+
196
234
  def save_config_value(
197
235
  section: str,
198
236
  key: str,
@@ -6,6 +6,7 @@
6
6
  Директория для логов ('logs') создается автоматически в корне проекта.
7
7
  """
8
8
 
9
+ import datetime
9
10
  import logging
10
11
  import logging.handlers
11
12
  import os
@@ -271,120 +272,210 @@ def _get_log_dir() -> Optional[str]:
271
272
 
272
273
  def setup_logger(
273
274
  name: str = 'app_logger',
275
+ config_section_name: Optional[str] = None,
274
276
  log_level: Optional[LogLevel] = None,
275
277
  log_file_name: Optional[str] = None,
276
278
  force_reconfigure: bool = False,
277
- rotation_type: str = 'time',
278
- max_bytes: int = 0,
279
- compress: bool = False,
280
- backup_count: int = 3
279
+ rotation_type: Optional[str] = None,
280
+ max_bytes: Optional[int] = None,
281
+ compress: Optional[bool] = None,
282
+ backup_count: Optional[int] = None,
283
+ encoding: Optional[str] = None,
284
+ when: Optional[str] = None,
285
+ interval: Optional[int] = None,
286
+ utc: Optional[bool] = None,
287
+ at_time: Optional[datetime.time] = None,
288
+ **kwargs: Any
281
289
  ) -> ChutilsLogger:
282
290
  """
283
- Настраивает и возвращает логгер, всегда обновляя его уровень.
291
+ Настраивает и возвращает логгер с гибким приоритетом конфигурации.
284
292
 
285
- При первом вызове для логгера с указанным именем она настраивает его
286
- обработчики (для консоли и файла). При последующих вызовах она только
287
- обновляет уровень логирования, не трогая обработчики, если не указан
288
- `force_reconfigure=True`.
293
+ Приоритет настроек:
294
+ 1. Явные аргументы, переданные в эту функцию.
295
+ 2. Объединенные настройки из `Logging.loggers.{name}` (если есть) поверх `Logging.default` (если есть).
296
+ 3. Для обратной совместимости: если `Logging.default` и `Logging.loggers` отсутствуют,
297
+ используются прямые настройки из `Logging` (как в старом формате).
298
+ 4. Значения по умолчанию, зашитые в коде.
289
299
 
290
300
  Args:
291
301
  name: Имя логгера. `app_logger` используется как стандартное имя.
302
+ config_section_name: Имя специфичной секции в конфиге (например, 'MyAuditLogger').
303
+ Если указана, настройки из этой секции переопределяют настройки из общей секции `[Logging]`.
304
+ Если не указана, используется только общая секция `[Logging]`.
292
305
  log_level: Явное указание уровня логирования (строкой или LogLevel).
293
306
  Если не задан, значение берется из конфигурационного файла.
294
307
  log_file_name: Имя файла для логирования. Если не указано, имя берется
295
308
  из конфигурации ('Logging', 'log_file_name').
296
309
  force_reconfigure: Если True, принудительно удаляет все существующие
297
310
  обработчики и настраивает логгер заново.
298
- rotation_type: Тип ротации: 'time' (ежедневная) или 'size'.
311
+ rotation_type: Тип ротации: 'time' или 'size'.
299
312
  max_bytes: Максимальный размер файла для ротации по 'size'.
300
313
  compress: Сжимать ли ротированные логи в .gz.
301
314
  backup_count: Количество хранимых ротированных файлов.
315
+ encoding: Кодировка файла (по умолчанию 'utf-8').
316
+ when: Для 'time'. Тип интервала ('S', 'M', 'H', 'D', 'midnight', 'W0'-'W6').
317
+ interval: Для 'time'. Длина интервала.
318
+ utc: Для 'time'. Использовать UTC время.
319
+ at_time: Для 'time'. Время ротации (при when='midnight').
320
+
321
+ **kwargs: Дополнительные параметры для FileHandler (например, `delay=True`, `errors='ignore'`, `mode='a'`).
302
322
 
303
323
  Returns:
304
324
  Настроенный экземпляр ChutilsLogger.
305
325
  """
306
326
  global _initialization_message_shown
307
327
  logger = logging.getLogger(name)
328
+ cfg = config.get_config()
308
329
 
309
- # --- 1. Определение и установка уровня логирования ---
310
- # Этот блок выполняется всегда, чтобы уровень можно было изменить в любой момент.
311
- cfg = config.get_config() # Загружаем конфигурацию
330
+ # --- Определение словаря настроек для данного логгера ---
331
+ # По умолчанию используем секцию [Logging]
332
+ default_settings = cfg.get('Logging', {})
312
333
 
313
- log_level_enum: LogLevel
314
- if isinstance(log_level, str):
315
- try:
316
- log_level_enum = LogLevel(log_level.upper())
317
- except ValueError:
318
- log_level_enum = LogLevel.INFO
319
- elif log_level is None:
320
- level_from_config = config.get_config_value('Logging', 'log_level', 'INFO', cfg)
321
- try:
322
- log_level_enum = LogLevel(level_from_config.upper())
323
- except ValueError:
324
- log_level_enum = LogLevel.INFO
334
+ # Если указана специфичная секция, ее настройки переопределяют дефолтные
335
+ specific_settings = {}
336
+ if config_section_name:
337
+ specific_settings = cfg.get(config_section_name, {})
338
+
339
+ final_logger_settings = {**default_settings, **specific_settings}
340
+
341
+ # --- 1. Определение и установка уровня логирования ---
342
+ # Приоритет: аргумент функции > настройки из конфига > 'INFO'
343
+ final_log_level_str: str
344
+ if log_level is not None:
345
+ final_log_level_str = log_level.value if isinstance(log_level, LogLevel) else str(log_level).upper()
325
346
  else:
326
- log_level_enum = log_level
347
+ level_val = final_logger_settings.get('log_level', 'INFO')
348
+ final_log_level_str = str(level_val).upper()
349
+
350
+ try:
351
+ log_level_enum = LogLevel(final_log_level_str)
352
+ level_int = getattr(logging, log_level_enum.value, logging.INFO)
353
+ except ValueError:
354
+ log_level_enum = LogLevel.INFO
355
+ level_int = logging.INFO
327
356
 
328
- level_int = getattr(logging, log_level_enum.value, logging.INFO)
329
357
  logger.setLevel(level_int)
330
358
  logger.propagate = False
331
359
  logging.debug("Уровень логирования для '%s' установлен на: %s (%s)", name, log_level_enum.value, level_int)
332
360
 
333
- # --- 2. Настройка обработчиков (только при необходимости) ---
361
+ # --- Настройка обработчиков (только при необходимости) ---
334
362
  if logger.hasHandlers() and not force_reconfigure:
335
363
  logging.debug("Обработчики для логгера '%s' уже настроены. Пропускаем настройку.", name)
336
364
  return logger # type: ignore
337
365
 
338
- # Если требуется принудительная перенастройка, очищаем старые обработчики
339
366
  if force_reconfigure:
340
367
  logging.debug("Принудительная перенастройка для '%s'. Удаление старых обработчиков...", name)
341
368
  for handler in logger.handlers[:]:
342
369
  handler.close()
343
370
  logger.removeHandler(handler)
344
371
 
345
- # Получаем директорию для логов.
346
- log_dir = _get_log_dir()
347
- logging.debug("setup_logger() получил log_dir: %s", log_dir)
348
-
349
- # Определяем имя файла лога
350
- if log_file_name is None:
351
- log_file_name = config.get_config_value('Logging', 'log_file_name', 'app.log', cfg)
352
- logging.debug("Имя файла лога для '%s' определено как: %s", name, log_file_name)
372
+ # --- Определение параметров на основе приоритетов ---
373
+ # Приоритет: аргумент функции > настройки из конфига > жестко заданное значение
374
+ final_log_file_name = log_file_name if log_file_name is not None else final_logger_settings.get('log_file_name',
375
+ 'app.log')
376
+ final_rotation_type = rotation_type if rotation_type is not None else final_logger_settings.get('rotation_type',
377
+ 'time')
378
+
379
+ # Для типизированных значений нужна безопасная обработка
380
+ try:
381
+ max_bytes_from_config = int(final_logger_settings.get('max_bytes', 5 * 1024 * 1024))
382
+ except (ValueError, TypeError):
383
+ max_bytes_from_config = 5 * 1024 * 1024
384
+ final_max_bytes = max_bytes if max_bytes is not None else max_bytes_from_config
385
+
386
+ compress_val = final_logger_settings.get('compress', False)
387
+ if isinstance(compress_val, str):
388
+ compress_from_config = compress_val.lower() in ['true', '1', 't', 'y', 'yes']
389
+ else:
390
+ compress_from_config = bool(compress_val)
391
+ final_compress = compress if compress is not None else compress_from_config
392
+
393
+ try:
394
+ backup_count_from_config = int(final_logger_settings.get('log_backup_count', 3))
395
+ except (ValueError, TypeError):
396
+ backup_count_from_config = 3
397
+ final_backup_count = backup_count if backup_count is not None else backup_count_from_config
398
+
399
+ final_encoding = encoding if encoding is not None else final_logger_settings.get('encoding', 'utf-8')
400
+ final_when = when if when is not None else final_logger_settings.get('when', 'D')
401
+
402
+ try:
403
+ interval_from_config = int(final_logger_settings.get('interval', 1))
404
+ except (ValueError, TypeError):
405
+ interval_from_config = 1
406
+ final_interval = interval if interval is not None else interval_from_config
407
+
408
+ utc_val = final_logger_settings.get('utc', False)
409
+ if isinstance(utc_val, str):
410
+ utc_from_config = utc_val.lower() in ['true', '1', 't', 'y', 'yes']
411
+ else:
412
+ utc_from_config = bool(utc_val)
413
+ final_utc = utc if utc is not None else utc_from_config
353
414
 
354
- # Определяем параметры ротации
355
- rotation_type = config.get_config_value('Logging', 'rotation_type', rotation_type, cfg)
356
- max_bytes = config.get_config_int('Logging', 'max_bytes', max_bytes, cfg)
357
- compress = config.get_config_boolean('Logging', 'compress', compress, cfg)
358
- backup_count = config.get_config_int('Logging', 'log_backup_count', 3, cfg)
415
+ final_at_time = at_time if at_time is not None else final_logger_settings.get('at_time', None)
359
416
 
417
+ # --- 4. Настройка обработчиков ---
418
+ log_dir = _get_log_dir()
360
419
  formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
361
420
 
362
- # Обработчик для вывода в консоль
363
421
  console_handler = logging.StreamHandler()
364
422
  console_handler.setFormatter(formatter)
365
423
  logger.addHandler(console_handler)
366
424
 
367
- # Обработчик для записи в файл
368
- if log_dir and log_file_name:
369
- log_file_path = Path(log_file_name) if Path(log_file_name).is_absolute() else Path(log_dir) / log_file_name
425
+ if log_dir and final_log_file_name:
426
+ log_file_path = Path(final_log_file_name) if Path(
427
+ final_log_file_name).is_absolute() else Path(log_dir) / final_log_file_name
370
428
  logging.debug("Попытка настроить файловый обработчик для %s в %s", name, log_file_path)
371
429
  try:
372
430
  file_handler: Optional[logging.FileHandler] = None
373
- if rotation_type == 'size':
374
- handler_class = CompressingRotatingFileHandler if compress else logging.handlers.RotatingFileHandler
375
- file_handler = handler_class(log_file_path, maxBytes=max_bytes, backupCount=backup_count, encoding='utf-8')
431
+ common_kwargs = {
432
+ 'encoding': final_encoding,
433
+ 'backupCount': final_backup_count,
434
+ }
435
+ common_kwargs.update(kwargs)
436
+
437
+ if final_rotation_type == 'size':
438
+ handler_class = CompressingRotatingFileHandler if final_compress else logging.handlers.RotatingFileHandler
439
+ rotation_kwargs = {'maxBytes': final_max_bytes}
440
+ final_kwargs = {**common_kwargs, **rotation_kwargs}
441
+ file_handler = handler_class(str(log_file_path), **final_kwargs)
376
442
  else: # 'time'
377
- handler_class = CompressingTimedRotatingFileHandler if compress else SafeTimedRotatingFileHandler
378
- file_handler = handler_class(log_file_path, when="D", interval=1, backupCount=backup_count, encoding='utf-8')
443
+ handler_class = CompressingTimedRotatingFileHandler if final_compress else SafeTimedRotatingFileHandler
444
+
445
+ rotation_kwargs = {
446
+ 'when': final_when,
447
+ 'interval': final_interval,
448
+ 'utc': final_utc,
449
+ }
450
+ # `at_time` может быть строкой из конфига, нужно преобразовать
451
+ if isinstance(final_at_time, str):
452
+ try:
453
+ final_at_time = datetime.time.fromisoformat(final_at_time)
454
+ except (TypeError, ValueError):
455
+ logger.error(
456
+ "Неверный формат времени '%s' для 'at_time' в конфиге. Используется None.", final_at_time)
457
+ final_at_time = None
458
+
459
+ if final_at_time is not None:
460
+ rotation_kwargs['atTime'] = final_at_time
461
+
462
+ final_kwargs = {**common_kwargs, **rotation_kwargs}
463
+
464
+ file_handler = handler_class(str(log_file_path), **final_kwargs)
379
465
 
380
466
  if file_handler:
381
467
  file_handler.setFormatter(formatter)
382
468
  logger.addHandler(file_handler)
383
469
 
384
470
  if not _initialization_message_shown:
471
+ info_msg = "."
472
+ if final_rotation_type == 'time':
473
+ info_msg = f", интервал: {final_interval}{final_when}"
474
+ else:
475
+ info_msg = f", макс. размер: {final_max_bytes}"
385
476
  logger.debug(
386
- "Логирование настроено. Уровень: %s. Файл: %s, ротация: %s, сжатие: %s.",
387
- log_level_enum.value, log_file_path, rotation_type, compress
477
+ "Логирование настроено. Уровень: %s. Файл: %s, ротация: %s, сжатие: %s%s",
478
+ log_level_enum.value, log_file_path, final_rotation_type, final_compress, info_msg
388
479
  )
389
480
  _initialization_message_shown = True
390
481
  except Exception as e:
File without changes
File without changes