echoss-common 1.0.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.
- echoss_common-1.0.0/MANIFEST.in +7 -0
- echoss_common-1.0.0/PKG-INFO +170 -0
- echoss_common-1.0.0/README.md +159 -0
- echoss_common-1.0.0/echoss_common/__init__.py +11 -0
- echoss_common-1.0.0/echoss_common/echoss_logger.py +154 -0
- echoss_common-1.0.0/echoss_common/fileformat.py +121 -0
- echoss_common-1.0.0/echoss_common.egg-info/PKG-INFO +170 -0
- echoss_common-1.0.0/echoss_common.egg-info/SOURCES.txt +11 -0
- echoss_common-1.0.0/echoss_common.egg-info/dependency_links.txt +1 -0
- echoss_common-1.0.0/echoss_common.egg-info/requires.txt +4 -0
- echoss_common-1.0.0/echoss_common.egg-info/top_level.txt +1 -0
- echoss_common-1.0.0/pyproject.toml +22 -0
- echoss_common-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: echoss-common
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Classifier: Programming Language :: Python :: 3
|
|
5
|
+
Classifier: Operating System :: OS Independent
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: pyyaml>6.0.2
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest; extra == "dev"
|
|
11
|
+
|
|
12
|
+
# echoss-common
|
|
13
|
+
|
|
14
|
+
echoss AI Bigdata Center 공통 유틸리티 라이브러리입니다.
|
|
15
|
+
로깅(Logger)과 파일 포맷(FileUtil) 기능을 제공합니다.
|
|
16
|
+
|
|
17
|
+
## 설치
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install echoss-common
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 요구사항
|
|
24
|
+
|
|
25
|
+
- Python >= 3.12
|
|
26
|
+
- pyyaml > 6.0.2
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 기능
|
|
31
|
+
|
|
32
|
+
### 1. Logger (`echoss_logger`)
|
|
33
|
+
|
|
34
|
+
타임드 로테이팅 파일 핸들러와 콘솔 핸들러를 지원하는 로거 유틸리티입니다.
|
|
35
|
+
|
|
36
|
+
#### 함수 목록
|
|
37
|
+
|
|
38
|
+
| 함수 | 설명 |
|
|
39
|
+
|------|------|
|
|
40
|
+
| `get_logger()` | 새 로거 생성 또는 기존 로거 갱신 |
|
|
41
|
+
| `use_logger()` | 이미 생성된 로거를 서브모듈에서 가져오기 |
|
|
42
|
+
| `set_logger_level()` | 로거 레벨 변경 |
|
|
43
|
+
| `modify_loggers_by_prefix()` | 이름 prefix로 여러 로거를 일괄 수정 |
|
|
44
|
+
|
|
45
|
+
#### 상수
|
|
46
|
+
|
|
47
|
+
| 상수 | 값 |
|
|
48
|
+
|------|----|
|
|
49
|
+
| `LOG_FORMAT_DETAIL` | `[날짜시간] 레벨 모듈.함수.라인 : 메시지` 형식 |
|
|
50
|
+
|
|
51
|
+
#### 사용 예시
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from echoss_common import get_logger, use_logger, set_logger_level, modify_loggers_by_prefix, LOG_FORMAT_DETAIL
|
|
55
|
+
|
|
56
|
+
# 기본 로거 생성 (콘솔 + 파일 출력)
|
|
57
|
+
logger = get_logger(
|
|
58
|
+
logger_name='myapp',
|
|
59
|
+
logger_format=LOG_FORMAT_DETAIL, # 상세 포맷 사용 (선택)
|
|
60
|
+
file_path='logs/myapp.log', # 로그 파일 경로 (기본: logs/echoss.log)
|
|
61
|
+
backup_count=7, # 보관할 로그 파일 수 (0이면 전부 보관)
|
|
62
|
+
use_console=True, # 콘솔 출력 여부
|
|
63
|
+
level='DEBUG' # 로그 레벨
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
logger.info("애플리케이션 시작")
|
|
67
|
+
logger.debug("디버그 메시지")
|
|
68
|
+
logger.error("오류 발생")
|
|
69
|
+
|
|
70
|
+
# 서브모듈에서 기존 로거 재사용
|
|
71
|
+
logger = use_logger('myapp')
|
|
72
|
+
logger.info("서브모듈에서 로거 사용")
|
|
73
|
+
|
|
74
|
+
# 로거 레벨만 변경
|
|
75
|
+
set_logger_level(logger, 'WARNING')
|
|
76
|
+
|
|
77
|
+
# prefix로 시작하는 모든 로거 일괄 수정
|
|
78
|
+
modify_loggers_by_prefix(
|
|
79
|
+
prefix='myapp',
|
|
80
|
+
new_format=LOG_FORMAT_DETAIL,
|
|
81
|
+
new_path='logs/myapp.log',
|
|
82
|
+
level='INFO'
|
|
83
|
+
)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### 2. FileUtil (`fileformat`)
|
|
89
|
+
|
|
90
|
+
설정 파일(YAML, JSON, XML, Properties)을 딕셔너리로 읽고 쓰는 유틸리티 클래스입니다.
|
|
91
|
+
|
|
92
|
+
#### 메서드 목록
|
|
93
|
+
|
|
94
|
+
| 메서드 | 설명 |
|
|
95
|
+
|--------|------|
|
|
96
|
+
| `FileUtil.dict_load()` | 설정 파일을 `dict`로 읽기 |
|
|
97
|
+
| `FileUtil.dict_dump()` | `dict`를 설정 파일로 쓰기 |
|
|
98
|
+
|
|
99
|
+
#### 지원 포맷
|
|
100
|
+
|
|
101
|
+
| 확장자 | 포맷 |
|
|
102
|
+
|--------|------|
|
|
103
|
+
| `.yaml`, `.yml` | YAML |
|
|
104
|
+
| `.json` | JSON |
|
|
105
|
+
| `.xml` | XML |
|
|
106
|
+
| `.properties` | Java Properties |
|
|
107
|
+
|
|
108
|
+
#### 사용 예시
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from echoss_common.fileformat import FileUtil
|
|
112
|
+
|
|
113
|
+
# YAML 파일 읽기
|
|
114
|
+
config = FileUtil.dict_load('config/settings.yaml')
|
|
115
|
+
print(config)
|
|
116
|
+
|
|
117
|
+
# JSON 파일 읽기
|
|
118
|
+
data = FileUtil.dict_load('config/params.json')
|
|
119
|
+
|
|
120
|
+
# Properties 파일 읽기
|
|
121
|
+
props = FileUtil.dict_load('config/app.properties')
|
|
122
|
+
|
|
123
|
+
# dict를 YAML 파일로 저장
|
|
124
|
+
settings = {'host': 'localhost', 'port': 8080, 'debug': True}
|
|
125
|
+
FileUtil.dict_dump(settings, 'config/settings.yaml')
|
|
126
|
+
|
|
127
|
+
# dict를 JSON 파일로 저장 (들여쓰기 지정 가능)
|
|
128
|
+
FileUtil.dict_dump(settings, 'config/settings.json', indent=2)
|
|
129
|
+
|
|
130
|
+
# 파일이 이미 존재할 때 덮어쓰기 방지
|
|
131
|
+
FileUtil.dict_dump(settings, 'config/settings.yaml', force_write=False)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## API 참조
|
|
137
|
+
|
|
138
|
+
### `get_logger(logger_name, logger_format, file_path, backup_count, use_console, level)`
|
|
139
|
+
|
|
140
|
+
| 파라미터 | 타입 | 기본값 | 설명 |
|
|
141
|
+
|----------|------|--------|------|
|
|
142
|
+
| `logger_name` | str | `'echoss'` | 로거 이름 |
|
|
143
|
+
| `logger_format` | str | `LOG_FORMAT` | 로그 포맷 문자열 |
|
|
144
|
+
| `file_path` | str | `'logs/echoss.log'` | 로그 파일 경로 (`None`이면 파일 미사용) |
|
|
145
|
+
| `backup_count` | int | `0` | 보관할 로테이팅 파일 수 (0이면 전부 보관) |
|
|
146
|
+
| `use_console` | bool | `True` | 콘솔 출력 여부 |
|
|
147
|
+
| `level` | str\|int | `'DEBUG'` | 로그 레벨 |
|
|
148
|
+
|
|
149
|
+
### `FileUtil.dict_load(file_path, file_format, **kwargs)`
|
|
150
|
+
|
|
151
|
+
| 파라미터 | 타입 | 기본값 | 설명 |
|
|
152
|
+
|----------|------|--------|------|
|
|
153
|
+
| `file_path` | str | — | 읽을 파일 경로 |
|
|
154
|
+
| `file_format` | str | `None` | 포맷 강제 지정 (미지정 시 확장자 자동 인식) |
|
|
155
|
+
|
|
156
|
+
### `FileUtil.dict_dump(config, file_path, file_format, force_write, xml_tag, **kwargs)`
|
|
157
|
+
|
|
158
|
+
| 파라미터 | 타입 | 기본값 | 설명 |
|
|
159
|
+
|----------|------|--------|------|
|
|
160
|
+
| `config` | dict | — | 저장할 딕셔너리 |
|
|
161
|
+
| `file_path` | str | — | 저장할 파일 경로 |
|
|
162
|
+
| `file_format` | str | `None` | 포맷 강제 지정 (미지정 시 확장자 자동 인식) |
|
|
163
|
+
| `force_write` | bool | `True` | 기존 파일 덮어쓰기 여부 |
|
|
164
|
+
| `xml_tag` | str | `None` | XML 저장 시 루트 태그 이름 |
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 라이선스
|
|
169
|
+
|
|
170
|
+
echoss AI Bigdata Center
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# echoss-common
|
|
2
|
+
|
|
3
|
+
echoss AI Bigdata Center 공통 유틸리티 라이브러리입니다.
|
|
4
|
+
로깅(Logger)과 파일 포맷(FileUtil) 기능을 제공합니다.
|
|
5
|
+
|
|
6
|
+
## 설치
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install echoss-common
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 요구사항
|
|
13
|
+
|
|
14
|
+
- Python >= 3.12
|
|
15
|
+
- pyyaml > 6.0.2
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 기능
|
|
20
|
+
|
|
21
|
+
### 1. Logger (`echoss_logger`)
|
|
22
|
+
|
|
23
|
+
타임드 로테이팅 파일 핸들러와 콘솔 핸들러를 지원하는 로거 유틸리티입니다.
|
|
24
|
+
|
|
25
|
+
#### 함수 목록
|
|
26
|
+
|
|
27
|
+
| 함수 | 설명 |
|
|
28
|
+
|------|------|
|
|
29
|
+
| `get_logger()` | 새 로거 생성 또는 기존 로거 갱신 |
|
|
30
|
+
| `use_logger()` | 이미 생성된 로거를 서브모듈에서 가져오기 |
|
|
31
|
+
| `set_logger_level()` | 로거 레벨 변경 |
|
|
32
|
+
| `modify_loggers_by_prefix()` | 이름 prefix로 여러 로거를 일괄 수정 |
|
|
33
|
+
|
|
34
|
+
#### 상수
|
|
35
|
+
|
|
36
|
+
| 상수 | 값 |
|
|
37
|
+
|------|----|
|
|
38
|
+
| `LOG_FORMAT_DETAIL` | `[날짜시간] 레벨 모듈.함수.라인 : 메시지` 형식 |
|
|
39
|
+
|
|
40
|
+
#### 사용 예시
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from echoss_common import get_logger, use_logger, set_logger_level, modify_loggers_by_prefix, LOG_FORMAT_DETAIL
|
|
44
|
+
|
|
45
|
+
# 기본 로거 생성 (콘솔 + 파일 출력)
|
|
46
|
+
logger = get_logger(
|
|
47
|
+
logger_name='myapp',
|
|
48
|
+
logger_format=LOG_FORMAT_DETAIL, # 상세 포맷 사용 (선택)
|
|
49
|
+
file_path='logs/myapp.log', # 로그 파일 경로 (기본: logs/echoss.log)
|
|
50
|
+
backup_count=7, # 보관할 로그 파일 수 (0이면 전부 보관)
|
|
51
|
+
use_console=True, # 콘솔 출력 여부
|
|
52
|
+
level='DEBUG' # 로그 레벨
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
logger.info("애플리케이션 시작")
|
|
56
|
+
logger.debug("디버그 메시지")
|
|
57
|
+
logger.error("오류 발생")
|
|
58
|
+
|
|
59
|
+
# 서브모듈에서 기존 로거 재사용
|
|
60
|
+
logger = use_logger('myapp')
|
|
61
|
+
logger.info("서브모듈에서 로거 사용")
|
|
62
|
+
|
|
63
|
+
# 로거 레벨만 변경
|
|
64
|
+
set_logger_level(logger, 'WARNING')
|
|
65
|
+
|
|
66
|
+
# prefix로 시작하는 모든 로거 일괄 수정
|
|
67
|
+
modify_loggers_by_prefix(
|
|
68
|
+
prefix='myapp',
|
|
69
|
+
new_format=LOG_FORMAT_DETAIL,
|
|
70
|
+
new_path='logs/myapp.log',
|
|
71
|
+
level='INFO'
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### 2. FileUtil (`fileformat`)
|
|
78
|
+
|
|
79
|
+
설정 파일(YAML, JSON, XML, Properties)을 딕셔너리로 읽고 쓰는 유틸리티 클래스입니다.
|
|
80
|
+
|
|
81
|
+
#### 메서드 목록
|
|
82
|
+
|
|
83
|
+
| 메서드 | 설명 |
|
|
84
|
+
|--------|------|
|
|
85
|
+
| `FileUtil.dict_load()` | 설정 파일을 `dict`로 읽기 |
|
|
86
|
+
| `FileUtil.dict_dump()` | `dict`를 설정 파일로 쓰기 |
|
|
87
|
+
|
|
88
|
+
#### 지원 포맷
|
|
89
|
+
|
|
90
|
+
| 확장자 | 포맷 |
|
|
91
|
+
|--------|------|
|
|
92
|
+
| `.yaml`, `.yml` | YAML |
|
|
93
|
+
| `.json` | JSON |
|
|
94
|
+
| `.xml` | XML |
|
|
95
|
+
| `.properties` | Java Properties |
|
|
96
|
+
|
|
97
|
+
#### 사용 예시
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from echoss_common.fileformat import FileUtil
|
|
101
|
+
|
|
102
|
+
# YAML 파일 읽기
|
|
103
|
+
config = FileUtil.dict_load('config/settings.yaml')
|
|
104
|
+
print(config)
|
|
105
|
+
|
|
106
|
+
# JSON 파일 읽기
|
|
107
|
+
data = FileUtil.dict_load('config/params.json')
|
|
108
|
+
|
|
109
|
+
# Properties 파일 읽기
|
|
110
|
+
props = FileUtil.dict_load('config/app.properties')
|
|
111
|
+
|
|
112
|
+
# dict를 YAML 파일로 저장
|
|
113
|
+
settings = {'host': 'localhost', 'port': 8080, 'debug': True}
|
|
114
|
+
FileUtil.dict_dump(settings, 'config/settings.yaml')
|
|
115
|
+
|
|
116
|
+
# dict를 JSON 파일로 저장 (들여쓰기 지정 가능)
|
|
117
|
+
FileUtil.dict_dump(settings, 'config/settings.json', indent=2)
|
|
118
|
+
|
|
119
|
+
# 파일이 이미 존재할 때 덮어쓰기 방지
|
|
120
|
+
FileUtil.dict_dump(settings, 'config/settings.yaml', force_write=False)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## API 참조
|
|
126
|
+
|
|
127
|
+
### `get_logger(logger_name, logger_format, file_path, backup_count, use_console, level)`
|
|
128
|
+
|
|
129
|
+
| 파라미터 | 타입 | 기본값 | 설명 |
|
|
130
|
+
|----------|------|--------|------|
|
|
131
|
+
| `logger_name` | str | `'echoss'` | 로거 이름 |
|
|
132
|
+
| `logger_format` | str | `LOG_FORMAT` | 로그 포맷 문자열 |
|
|
133
|
+
| `file_path` | str | `'logs/echoss.log'` | 로그 파일 경로 (`None`이면 파일 미사용) |
|
|
134
|
+
| `backup_count` | int | `0` | 보관할 로테이팅 파일 수 (0이면 전부 보관) |
|
|
135
|
+
| `use_console` | bool | `True` | 콘솔 출력 여부 |
|
|
136
|
+
| `level` | str\|int | `'DEBUG'` | 로그 레벨 |
|
|
137
|
+
|
|
138
|
+
### `FileUtil.dict_load(file_path, file_format, **kwargs)`
|
|
139
|
+
|
|
140
|
+
| 파라미터 | 타입 | 기본값 | 설명 |
|
|
141
|
+
|----------|------|--------|------|
|
|
142
|
+
| `file_path` | str | — | 읽을 파일 경로 |
|
|
143
|
+
| `file_format` | str | `None` | 포맷 강제 지정 (미지정 시 확장자 자동 인식) |
|
|
144
|
+
|
|
145
|
+
### `FileUtil.dict_dump(config, file_path, file_format, force_write, xml_tag, **kwargs)`
|
|
146
|
+
|
|
147
|
+
| 파라미터 | 타입 | 기본값 | 설명 |
|
|
148
|
+
|----------|------|--------|------|
|
|
149
|
+
| `config` | dict | — | 저장할 딕셔너리 |
|
|
150
|
+
| `file_path` | str | — | 저장할 파일 경로 |
|
|
151
|
+
| `file_format` | str | `None` | 포맷 강제 지정 (미지정 시 확장자 자동 인식) |
|
|
152
|
+
| `force_write` | bool | `True` | 기존 파일 덮어쓰기 여부 |
|
|
153
|
+
| `xml_tag` | str | `None` | XML 저장 시 루트 태그 이름 |
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 라이선스
|
|
158
|
+
|
|
159
|
+
echoss AI Bigdata Center
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# for v1.0
|
|
2
|
+
from .echoss_logger import get_logger, use_logger, LOG_FORMAT_DETAIL, set_logger_level, modify_loggers_by_prefix
|
|
3
|
+
from .fileformat import FileUtil
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"get_logger",
|
|
7
|
+
"use_logger",
|
|
8
|
+
"LOG_FORMAT_DETAIL",
|
|
9
|
+
"set_logger_level",
|
|
10
|
+
"modify_loggers_by_prefix"
|
|
11
|
+
]
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from logging.handlers import TimedRotatingFileHandler
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
LOG_FORMAT = "[%(asctime)s.%(msecs)03d] %(levelname)-7s %(name)s : %(message)s"
|
|
8
|
+
LOG_FORMAT_DETAIL = "[%(asctime)s.%(msecs)03d] %(levelname)-7s %(module)s.%(funcName)s.%(lineno)d : %(message)s"
|
|
9
|
+
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
|
10
|
+
|
|
11
|
+
LOG_FILE = "logs/echoss.log"
|
|
12
|
+
|
|
13
|
+
# Set environment variable for Python IO encoding
|
|
14
|
+
os.environ["PYTHONIOENCODING"] = "utf-8"
|
|
15
|
+
|
|
16
|
+
def _get_console_handler(formatter):
|
|
17
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
|
18
|
+
console_handler.setFormatter(formatter)
|
|
19
|
+
# set utf-8 encoding
|
|
20
|
+
return console_handler
|
|
21
|
+
|
|
22
|
+
def _get_app(formatter):
|
|
23
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
|
24
|
+
console_handler.setFormatter(formatter)
|
|
25
|
+
# set utf-8 encoding
|
|
26
|
+
return console_handler
|
|
27
|
+
|
|
28
|
+
def _get_file_handler(formatter, file_path, backup_count):
|
|
29
|
+
file_handler = TimedRotatingFileHandler(file_path, when="midnight", backupCount=backup_count, encoding='utf-8')
|
|
30
|
+
file_handler.setFormatter(formatter)
|
|
31
|
+
return file_handler
|
|
32
|
+
|
|
33
|
+
def use_logger(logger_name='echoss'):
|
|
34
|
+
"""get parent logger in submodule python script
|
|
35
|
+
:param logger_name: logger name
|
|
36
|
+
|
|
37
|
+
:returns: logger of logger_name
|
|
38
|
+
:raises RuntimeError when logger_name is not made
|
|
39
|
+
"""
|
|
40
|
+
logger = logging.getLogger(logger_name)
|
|
41
|
+
|
|
42
|
+
if logger.hasHandlers():
|
|
43
|
+
return logger
|
|
44
|
+
else:
|
|
45
|
+
raise RuntimeError(f"{logger_name} is not created yet")
|
|
46
|
+
|
|
47
|
+
def get_logger(logger_name='echoss', logger_format=LOG_FORMAT, file_path=LOG_FILE, backup_count=0,
|
|
48
|
+
use_console=True, level="DEBUG"):
|
|
49
|
+
"""
|
|
50
|
+
create new echoss logger or replace old logger
|
|
51
|
+
:param logger_name: logger name
|
|
52
|
+
:param logger_format: logging format
|
|
53
|
+
:param file_path: file path if File logging else None
|
|
54
|
+
:param backup_count: if 0 keep all timed rotating log files, else only keep backup count number of files
|
|
55
|
+
:param use_console: use console output or not
|
|
56
|
+
:param level: (str or int) set logging level
|
|
57
|
+
|
|
58
|
+
:returns: generated logger
|
|
59
|
+
:raises None
|
|
60
|
+
"""
|
|
61
|
+
logger = logging.getLogger(logger_name)
|
|
62
|
+
formatter = logging.Formatter(logger_format, datefmt=DATE_FORMAT)
|
|
63
|
+
set_logger_level(logger, level)
|
|
64
|
+
# with this pattern, it's rarely necessary to propagate the error up to parent
|
|
65
|
+
logger.propagate = False
|
|
66
|
+
|
|
67
|
+
# make file_path parent directory
|
|
68
|
+
parent_dir = os.path.dirname(file_path)
|
|
69
|
+
if parent_dir:
|
|
70
|
+
os.makedirs(parent_dir, exist_ok=True)
|
|
71
|
+
|
|
72
|
+
# Update or add console handler
|
|
73
|
+
has_console_handler = False
|
|
74
|
+
for handler in logger.handlers:
|
|
75
|
+
if not isinstance(handler, TimedRotatingFileHandler) and isinstance(handler, logging.StreamHandler):
|
|
76
|
+
if use_console:
|
|
77
|
+
handler.setFormatter(formatter)
|
|
78
|
+
has_console_handler = True
|
|
79
|
+
else:
|
|
80
|
+
# Remove StreamHandler
|
|
81
|
+
logger.removeHandler(handler)
|
|
82
|
+
handler.close()
|
|
83
|
+
|
|
84
|
+
if use_console and not has_console_handler:
|
|
85
|
+
logger.addHandler(_get_console_handler(formatter))
|
|
86
|
+
|
|
87
|
+
# Update or add file handler
|
|
88
|
+
file_handler_exists = False
|
|
89
|
+
for handler in logger.handlers:
|
|
90
|
+
if isinstance(handler, TimedRotatingFileHandler):
|
|
91
|
+
# Check if the existing handler path matches the current file_path
|
|
92
|
+
if handler.baseFilename == file_path:
|
|
93
|
+
handler.setFormatter(formatter)
|
|
94
|
+
file_handler_exists = True
|
|
95
|
+
else:
|
|
96
|
+
# Remove the old handler and close it
|
|
97
|
+
logger.removeHandler(handler)
|
|
98
|
+
handler.close()
|
|
99
|
+
|
|
100
|
+
if file_path and not file_handler_exists:
|
|
101
|
+
logger.addHandler(_get_file_handler(formatter, file_path, backup_count))
|
|
102
|
+
|
|
103
|
+
return logger
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def set_logger_level(logger, level):
|
|
107
|
+
"""
|
|
108
|
+
:param logger: logger generated by get_logger()
|
|
109
|
+
:param level: string like "DEBUG" or "INFO" or integer logging.DEBUG or logging.INFO
|
|
110
|
+
:return: None
|
|
111
|
+
:raises ValueError or TypeError if level invalid
|
|
112
|
+
"""
|
|
113
|
+
if isinstance(level, str):
|
|
114
|
+
level = level.upper()
|
|
115
|
+
numeric_level = getattr(logging, level, None)
|
|
116
|
+
if not isinstance(numeric_level, int):
|
|
117
|
+
raise ValueError(f"Invalid log level: {level}")
|
|
118
|
+
elif isinstance(level, int):
|
|
119
|
+
numeric_level = level
|
|
120
|
+
else:
|
|
121
|
+
raise TypeError("Level must be an integer or string")
|
|
122
|
+
|
|
123
|
+
logger.setLevel(numeric_level)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def modify_loggers_by_prefix(prefix, new_format: str=LOG_FORMAT, new_path: str=LOG_FILE,
|
|
127
|
+
backup_count: int=0, use_console=True, level="DEBUG"):
|
|
128
|
+
"""
|
|
129
|
+
Modify loggers that logger name starting with a specific prefix.
|
|
130
|
+
:param prefix: prefix of lgger name
|
|
131
|
+
:param new_format: new logging format
|
|
132
|
+
:param new_path: new file path if File logging else None
|
|
133
|
+
:param backup_count: if 0 keep all timed rotating log files, else only keep backup count number of files
|
|
134
|
+
:param use_console: use console output or not
|
|
135
|
+
:param level: set logging level by level name or level number
|
|
136
|
+
:return: first logger
|
|
137
|
+
:raises None
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
# Iterate over all loggers in the logging manager
|
|
141
|
+
is_first = True
|
|
142
|
+
first_logger = None
|
|
143
|
+
for logger_name in logging.Logger.manager.loggerDict.keys():
|
|
144
|
+
if logger_name.startswith(prefix):
|
|
145
|
+
current_logger = get_logger(logger_name, logger_format=new_format, file_path=new_path,
|
|
146
|
+
backup_count=backup_count, use_console=use_console, level=level)
|
|
147
|
+
|
|
148
|
+
if is_first:
|
|
149
|
+
is_first = False
|
|
150
|
+
first_logger = current_logger
|
|
151
|
+
if backup_count != 0:
|
|
152
|
+
backup_count = 0
|
|
153
|
+
|
|
154
|
+
return first_logger
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""
|
|
2
|
+
echoss AI Bigdata Center Solution - file format utilty (static version)
|
|
3
|
+
"""
|
|
4
|
+
import configparser
|
|
5
|
+
import io
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
from echoss_common import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger("echoss_fileformat")
|
|
13
|
+
EMPTY_DICT = dict()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
load/dump config file
|
|
18
|
+
"""
|
|
19
|
+
def dict_load(file_path: str, file_format: str = None, **kwargs) -> dict:
|
|
20
|
+
"""config file read to dict
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
file_path (str): 파일명
|
|
24
|
+
file_format : file extension name to read
|
|
25
|
+
kwargs : keyword arguments
|
|
26
|
+
Return:
|
|
27
|
+
dict
|
|
28
|
+
"""
|
|
29
|
+
if file_format is None:
|
|
30
|
+
_, ext = os.path.splitext(file_path)
|
|
31
|
+
file_format = ext[1:].lower() if ext else None
|
|
32
|
+
if not os.path.exists(file_path):
|
|
33
|
+
logger.error(f"load config file [{file_path}] is not exist")
|
|
34
|
+
return EMPTY_DICT
|
|
35
|
+
|
|
36
|
+
if "yaml" == file_format or "yml" == file_format :
|
|
37
|
+
with open(file_path, 'r') as f:
|
|
38
|
+
yaml_dict = yaml.safe_load(f)
|
|
39
|
+
return yaml_dict
|
|
40
|
+
elif "json" == file_format:
|
|
41
|
+
with open(file_path, 'r') as f:
|
|
42
|
+
return json.load(f, **kwargs)
|
|
43
|
+
elif "xml" == file_format:
|
|
44
|
+
logger.error(f"Unsupported file format: {file_format}")
|
|
45
|
+
return EMPTY_DICT
|
|
46
|
+
elif 'properties' == file_format:
|
|
47
|
+
config = configparser.ConfigParser()
|
|
48
|
+
properties_dict = {}
|
|
49
|
+
with open(file_path, 'r') as f:
|
|
50
|
+
file_content = f.read()
|
|
51
|
+
if not file_content.strip().startswith('['):
|
|
52
|
+
file_content = '[DEFAULT]\n' + file_content
|
|
53
|
+
config.read_string(file_content)
|
|
54
|
+
|
|
55
|
+
if config.sections(): # Check if there are any sections
|
|
56
|
+
for section in config.sections():
|
|
57
|
+
properties_dict[section] = dict(config.items(section))
|
|
58
|
+
else:
|
|
59
|
+
properties_dict = dict(config.items(config.default_section))
|
|
60
|
+
|
|
61
|
+
return properties_dict
|
|
62
|
+
else:
|
|
63
|
+
logger.error(f"Unsupported file format [{file_format}]")
|
|
64
|
+
return EMPTY_DICT
|
|
65
|
+
|
|
66
|
+
def dict_dump(config: dict, file_path: str, file_format=None, force_write=True, xml_tag=None, **kwargs):
|
|
67
|
+
"""config dict write to file
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
config (dict): config dictionary
|
|
71
|
+
file_path (str): 파일명
|
|
72
|
+
file_format : file extension name to use
|
|
73
|
+
force_write : if True overwrite exist file or just return
|
|
74
|
+
xml_tag (str) : root tag to wrapping dictionary
|
|
75
|
+
kwargs : keyword arguments
|
|
76
|
+
"""
|
|
77
|
+
if file_format is None:
|
|
78
|
+
_, ext = os.path.splitext(file_path)
|
|
79
|
+
file_format = ext[1:].lower() if ext else None
|
|
80
|
+
|
|
81
|
+
if os.path.exists(file_path) and force_write is False:
|
|
82
|
+
logger.error(f"Can not overwrite exist config file [{file_path}] use force_write=True if need")
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
if "yaml" == file_format or "yml" == file_format :
|
|
86
|
+
with open(file_path, 'w') as f:
|
|
87
|
+
yaml.dump(config, f, default_style=False, allow_unicode=True)
|
|
88
|
+
elif "json" == file_format:
|
|
89
|
+
with open(file_path, 'w') as f:
|
|
90
|
+
indent = kwargs.pop('indent', 4)
|
|
91
|
+
json.dump(config, f, indent=indent, **kwargs)
|
|
92
|
+
elif "xml" == file_format:
|
|
93
|
+
logger.error(f"Unsupported file format: {file_format}")
|
|
94
|
+
return EMPTY_DICT
|
|
95
|
+
elif 'properties' == file_format:
|
|
96
|
+
parser = configparser.ConfigParser()
|
|
97
|
+
have_section = any(isinstance(v, dict) for v in config.values())
|
|
98
|
+
if have_section:
|
|
99
|
+
for section, params in config.items():
|
|
100
|
+
if isinstance(params, dict):
|
|
101
|
+
parser.add_section(section)
|
|
102
|
+
for k, v in params.items():
|
|
103
|
+
parser.set(section, k, str(v))
|
|
104
|
+
else:
|
|
105
|
+
parser.set('DEFAULT', section, str(params))
|
|
106
|
+
else:
|
|
107
|
+
# parser.add_section('DEFAULT')
|
|
108
|
+
for k, v in config.items():
|
|
109
|
+
parser.set('DEFAULT', k, str(v))
|
|
110
|
+
with open(file_path, 'w') as f:
|
|
111
|
+
if have_section:
|
|
112
|
+
parser.write(f)
|
|
113
|
+
else:
|
|
114
|
+
with io.StringIO() as string_file:
|
|
115
|
+
parser.write(string_file)
|
|
116
|
+
clean_text = string_file.getvalue().replace("[DEFAULT]\n", "")
|
|
117
|
+
f.write(clean_text)
|
|
118
|
+
else:
|
|
119
|
+
logger.error(f"Unsupported file format [{file_format}]")
|
|
120
|
+
|
|
121
|
+
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: echoss-common
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Classifier: Programming Language :: Python :: 3
|
|
5
|
+
Classifier: Operating System :: OS Independent
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: pyyaml>6.0.2
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest; extra == "dev"
|
|
11
|
+
|
|
12
|
+
# echoss-common
|
|
13
|
+
|
|
14
|
+
echoss AI Bigdata Center 공통 유틸리티 라이브러리입니다.
|
|
15
|
+
로깅(Logger)과 파일 포맷(FileUtil) 기능을 제공합니다.
|
|
16
|
+
|
|
17
|
+
## 설치
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install echoss-common
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 요구사항
|
|
24
|
+
|
|
25
|
+
- Python >= 3.12
|
|
26
|
+
- pyyaml > 6.0.2
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 기능
|
|
31
|
+
|
|
32
|
+
### 1. Logger (`echoss_logger`)
|
|
33
|
+
|
|
34
|
+
타임드 로테이팅 파일 핸들러와 콘솔 핸들러를 지원하는 로거 유틸리티입니다.
|
|
35
|
+
|
|
36
|
+
#### 함수 목록
|
|
37
|
+
|
|
38
|
+
| 함수 | 설명 |
|
|
39
|
+
|------|------|
|
|
40
|
+
| `get_logger()` | 새 로거 생성 또는 기존 로거 갱신 |
|
|
41
|
+
| `use_logger()` | 이미 생성된 로거를 서브모듈에서 가져오기 |
|
|
42
|
+
| `set_logger_level()` | 로거 레벨 변경 |
|
|
43
|
+
| `modify_loggers_by_prefix()` | 이름 prefix로 여러 로거를 일괄 수정 |
|
|
44
|
+
|
|
45
|
+
#### 상수
|
|
46
|
+
|
|
47
|
+
| 상수 | 값 |
|
|
48
|
+
|------|----|
|
|
49
|
+
| `LOG_FORMAT_DETAIL` | `[날짜시간] 레벨 모듈.함수.라인 : 메시지` 형식 |
|
|
50
|
+
|
|
51
|
+
#### 사용 예시
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from echoss_common import get_logger, use_logger, set_logger_level, modify_loggers_by_prefix, LOG_FORMAT_DETAIL
|
|
55
|
+
|
|
56
|
+
# 기본 로거 생성 (콘솔 + 파일 출력)
|
|
57
|
+
logger = get_logger(
|
|
58
|
+
logger_name='myapp',
|
|
59
|
+
logger_format=LOG_FORMAT_DETAIL, # 상세 포맷 사용 (선택)
|
|
60
|
+
file_path='logs/myapp.log', # 로그 파일 경로 (기본: logs/echoss.log)
|
|
61
|
+
backup_count=7, # 보관할 로그 파일 수 (0이면 전부 보관)
|
|
62
|
+
use_console=True, # 콘솔 출력 여부
|
|
63
|
+
level='DEBUG' # 로그 레벨
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
logger.info("애플리케이션 시작")
|
|
67
|
+
logger.debug("디버그 메시지")
|
|
68
|
+
logger.error("오류 발생")
|
|
69
|
+
|
|
70
|
+
# 서브모듈에서 기존 로거 재사용
|
|
71
|
+
logger = use_logger('myapp')
|
|
72
|
+
logger.info("서브모듈에서 로거 사용")
|
|
73
|
+
|
|
74
|
+
# 로거 레벨만 변경
|
|
75
|
+
set_logger_level(logger, 'WARNING')
|
|
76
|
+
|
|
77
|
+
# prefix로 시작하는 모든 로거 일괄 수정
|
|
78
|
+
modify_loggers_by_prefix(
|
|
79
|
+
prefix='myapp',
|
|
80
|
+
new_format=LOG_FORMAT_DETAIL,
|
|
81
|
+
new_path='logs/myapp.log',
|
|
82
|
+
level='INFO'
|
|
83
|
+
)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### 2. FileUtil (`fileformat`)
|
|
89
|
+
|
|
90
|
+
설정 파일(YAML, JSON, XML, Properties)을 딕셔너리로 읽고 쓰는 유틸리티 클래스입니다.
|
|
91
|
+
|
|
92
|
+
#### 메서드 목록
|
|
93
|
+
|
|
94
|
+
| 메서드 | 설명 |
|
|
95
|
+
|--------|------|
|
|
96
|
+
| `FileUtil.dict_load()` | 설정 파일을 `dict`로 읽기 |
|
|
97
|
+
| `FileUtil.dict_dump()` | `dict`를 설정 파일로 쓰기 |
|
|
98
|
+
|
|
99
|
+
#### 지원 포맷
|
|
100
|
+
|
|
101
|
+
| 확장자 | 포맷 |
|
|
102
|
+
|--------|------|
|
|
103
|
+
| `.yaml`, `.yml` | YAML |
|
|
104
|
+
| `.json` | JSON |
|
|
105
|
+
| `.xml` | XML |
|
|
106
|
+
| `.properties` | Java Properties |
|
|
107
|
+
|
|
108
|
+
#### 사용 예시
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from echoss_common.fileformat import FileUtil
|
|
112
|
+
|
|
113
|
+
# YAML 파일 읽기
|
|
114
|
+
config = FileUtil.dict_load('config/settings.yaml')
|
|
115
|
+
print(config)
|
|
116
|
+
|
|
117
|
+
# JSON 파일 읽기
|
|
118
|
+
data = FileUtil.dict_load('config/params.json')
|
|
119
|
+
|
|
120
|
+
# Properties 파일 읽기
|
|
121
|
+
props = FileUtil.dict_load('config/app.properties')
|
|
122
|
+
|
|
123
|
+
# dict를 YAML 파일로 저장
|
|
124
|
+
settings = {'host': 'localhost', 'port': 8080, 'debug': True}
|
|
125
|
+
FileUtil.dict_dump(settings, 'config/settings.yaml')
|
|
126
|
+
|
|
127
|
+
# dict를 JSON 파일로 저장 (들여쓰기 지정 가능)
|
|
128
|
+
FileUtil.dict_dump(settings, 'config/settings.json', indent=2)
|
|
129
|
+
|
|
130
|
+
# 파일이 이미 존재할 때 덮어쓰기 방지
|
|
131
|
+
FileUtil.dict_dump(settings, 'config/settings.yaml', force_write=False)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## API 참조
|
|
137
|
+
|
|
138
|
+
### `get_logger(logger_name, logger_format, file_path, backup_count, use_console, level)`
|
|
139
|
+
|
|
140
|
+
| 파라미터 | 타입 | 기본값 | 설명 |
|
|
141
|
+
|----------|------|--------|------|
|
|
142
|
+
| `logger_name` | str | `'echoss'` | 로거 이름 |
|
|
143
|
+
| `logger_format` | str | `LOG_FORMAT` | 로그 포맷 문자열 |
|
|
144
|
+
| `file_path` | str | `'logs/echoss.log'` | 로그 파일 경로 (`None`이면 파일 미사용) |
|
|
145
|
+
| `backup_count` | int | `0` | 보관할 로테이팅 파일 수 (0이면 전부 보관) |
|
|
146
|
+
| `use_console` | bool | `True` | 콘솔 출력 여부 |
|
|
147
|
+
| `level` | str\|int | `'DEBUG'` | 로그 레벨 |
|
|
148
|
+
|
|
149
|
+
### `FileUtil.dict_load(file_path, file_format, **kwargs)`
|
|
150
|
+
|
|
151
|
+
| 파라미터 | 타입 | 기본값 | 설명 |
|
|
152
|
+
|----------|------|--------|------|
|
|
153
|
+
| `file_path` | str | — | 읽을 파일 경로 |
|
|
154
|
+
| `file_format` | str | `None` | 포맷 강제 지정 (미지정 시 확장자 자동 인식) |
|
|
155
|
+
|
|
156
|
+
### `FileUtil.dict_dump(config, file_path, file_format, force_write, xml_tag, **kwargs)`
|
|
157
|
+
|
|
158
|
+
| 파라미터 | 타입 | 기본값 | 설명 |
|
|
159
|
+
|----------|------|--------|------|
|
|
160
|
+
| `config` | dict | — | 저장할 딕셔너리 |
|
|
161
|
+
| `file_path` | str | — | 저장할 파일 경로 |
|
|
162
|
+
| `file_format` | str | `None` | 포맷 강제 지정 (미지정 시 확장자 자동 인식) |
|
|
163
|
+
| `force_write` | bool | `True` | 기존 파일 덮어쓰기 여부 |
|
|
164
|
+
| `xml_tag` | str | `None` | XML 저장 시 루트 태그 이름 |
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 라이선스
|
|
169
|
+
|
|
170
|
+
echoss AI Bigdata Center
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
echoss_common/__init__.py
|
|
5
|
+
echoss_common/echoss_logger.py
|
|
6
|
+
echoss_common/fileformat.py
|
|
7
|
+
echoss_common.egg-info/PKG-INFO
|
|
8
|
+
echoss_common.egg-info/SOURCES.txt
|
|
9
|
+
echoss_common.egg-info/dependency_links.txt
|
|
10
|
+
echoss_common.egg-info/requires.txt
|
|
11
|
+
echoss_common.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
echoss_common
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=80.9.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name="echoss-common"
|
|
7
|
+
version="1.0.0"
|
|
8
|
+
description=""
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Operating System :: OS Independent"
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
requires-python='>=3.12'
|
|
17
|
+
dependencies=[
|
|
18
|
+
"pyyaml>6.0.2"
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
dev = ["pytest"]
|