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.
- {chutils-2.2.1 → chutils-2.3.0}/PKG-INFO +49 -10
- {chutils-2.2.1 → chutils-2.3.0}/README.md +48 -9
- {chutils-2.2.1 → chutils-2.3.0}/pyproject.toml +2 -1
- {chutils-2.2.1 → chutils-2.3.0}/src/chutils/config.py +39 -1
- {chutils-2.2.1 → chutils-2.3.0}/src/chutils/logger.py +144 -53
- {chutils-2.2.1 → chutils-2.3.0}/LICENSE +0 -0
- {chutils-2.2.1 → chutils-2.3.0}/src/chutils/__init__.py +0 -0
- {chutils-2.2.1 → chutils-2.3.0}/src/chutils/decorators.py +0 -0
- {chutils-2.2.1 → chutils-2.3.0}/src/chutils/secret_manager.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chutils
|
|
3
|
-
Version: 2.
|
|
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
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
278
|
-
max_bytes: int =
|
|
279
|
-
compress: bool =
|
|
280
|
-
backup_count: int =
|
|
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
|
-
`
|
|
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'
|
|
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
|
-
# ---
|
|
310
|
-
#
|
|
311
|
-
|
|
330
|
+
# --- Определение словаря настроек для данного логгера ---
|
|
331
|
+
# По умолчанию используем секцию [Logging]
|
|
332
|
+
default_settings = cfg.get('Logging', {})
|
|
312
333
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
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
|
-
# ---
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
-
|
|
369
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
|
378
|
-
|
|
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,
|
|
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
|
|
File without changes
|
|
File without changes
|