agesuta 0.1.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.
- agesuta-0.1.0/LICENSE +21 -0
- agesuta-0.1.0/PKG-INFO +112 -0
- agesuta-0.1.0/README.md +85 -0
- agesuta-0.1.0/agesuta/__init__.py +5 -0
- agesuta-0.1.0/agesuta/com.py +344 -0
- agesuta-0.1.0/agesuta/configmanager.py +430 -0
- agesuta-0.1.0/agesuta/slackapi.py +252 -0
- agesuta-0.1.0/agesuta.egg-info/PKG-INFO +112 -0
- agesuta-0.1.0/agesuta.egg-info/SOURCES.txt +12 -0
- agesuta-0.1.0/agesuta.egg-info/dependency_links.txt +1 -0
- agesuta-0.1.0/agesuta.egg-info/requires.txt +11 -0
- agesuta-0.1.0/agesuta.egg-info/top_level.txt +1 -0
- agesuta-0.1.0/setup.cfg +4 -0
- agesuta-0.1.0/setup.py +51 -0
agesuta-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 AgemameSutachi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
agesuta-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: agesuta
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A custom logging utility and other utilities with Rich console output, file handling, Slack notification, etc.
|
|
5
|
+
Home-page: https://github.com/AgemameSutachi/agesuta
|
|
6
|
+
Author: AgemameSutachi
|
|
7
|
+
Author-email: your.email@example.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: logging custom logger rich file rotation slack requests utility
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Classifier: Topic :: System :: Logging
|
|
14
|
+
Classifier: Topic :: Communications :: Chat
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Requires-Python: >=3.6
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
|
|
28
|
+
# agesuta
|
|
29
|
+
|
|
30
|
+
## README.txtはAIによる生成ファイルです。(README.txt is a file generated by AI.)
|
|
31
|
+
|
|
32
|
+
Python標準の`logging`モジュールをベースにしたカスタムロギングユーティリティです。`rich`による色付きコンソール出力、カスタムローテーションハンドラによるファイルロギング、ログファイルのエンコーディング処理機能を備えています。
|
|
33
|
+
|
|
34
|
+
**注記:** 現在のバージョンに含まれるカスタムファイルローテーションハンドラ(`CustomLevelRotatingFileHandler`, `CustomDateRotatingFileHandler`)は、`CustomLogger.log_main`で生成されるファイル名との間に論理的な不整合がある可能性があります。これにより、一部の設定でログファイルが正しくローテーションされない場合があります。使用には注意が必要か、ハンドラの実装を確認してください。
|
|
35
|
+
|
|
36
|
+
## インストール
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install agesuta
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 使用方法
|
|
43
|
+
|
|
44
|
+
**基本的な使用例を以下に示します:**
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import logging
|
|
48
|
+
from agesuta import CustomLogger, log_decorator
|
|
49
|
+
|
|
50
|
+
# ロガーの初期設定を行うインスタンスを作成します
|
|
51
|
+
# これにより、ルートロガーにハンドラが設定されます
|
|
52
|
+
Cl_logger = CustomLogger(
|
|
53
|
+
flag_datelog=False, # 日付ベースの単一ログファイルにする場合は True に設定
|
|
54
|
+
dir_path="./Log",
|
|
55
|
+
log_encode="utf-8",
|
|
56
|
+
showlevel="INFO", # コンソール出力の最小レベル
|
|
57
|
+
maxBytes=5 * 1024 * 1024, # ログファイルを5MBでローテーション
|
|
58
|
+
backupCount=5 # バックアップファイルを最大5世代保持
|
|
59
|
+
)
|
|
60
|
+
Cl_logger.log_main() # ロギング設定を適用します
|
|
61
|
+
|
|
62
|
+
# 標準のlogging.getLogger()を使ってロガーインスタンスを取得します
|
|
63
|
+
# これは Cl_logger.log_main() で設定されたハンドラを使用します
|
|
64
|
+
logger = logging.getLogger(__name__)
|
|
65
|
+
|
|
66
|
+
logger.debug("これはデバッグメッセージです。")
|
|
67
|
+
logger.info("これは情報メッセージです。")
|
|
68
|
+
logger.warning("これは警告メッセージです。")
|
|
69
|
+
logger.error("これはエラーメッセージです。")
|
|
70
|
+
logger.critical("これはクリティカルメッセージです。")
|
|
71
|
+
|
|
72
|
+
# log_decorator の使用例
|
|
73
|
+
# from agesuta import log_decorator # パッケージ名に合わせて変更してください
|
|
74
|
+
|
|
75
|
+
# @log_decorator(logger)
|
|
76
|
+
# def my_function(arg1, arg2):
|
|
77
|
+
# logger.info("関数の中で何か処理しています")
|
|
78
|
+
# return arg1 + arg2
|
|
79
|
+
|
|
80
|
+
# result = my_function(10, 20)
|
|
81
|
+
# print(f"関数の結果: {result}")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`flag_datelog` が `False` の場合、異なるレベル(例: `my_script_0_debug.log`, `my_script_1_info.log` など)ごとに個別のログファイルが `dir_path` に作成されます。
|
|
85
|
+
`flag_datelog` が `True` の場合、日付を含む単一のログファイル(例: `my_script_YYYY-MM-DD.log`)が作成されます。
|
|
86
|
+
|
|
87
|
+
## CustomLogger パラメータ
|
|
88
|
+
|
|
89
|
+
- `flag_datelog` (`bool`): `True`の場合、日付ベースのログファイル命名規則(`basename_YYYY-MM-DD.log`)を使用し、`CustomDateRotatingFileHandler`を使用します。`False`(デフォルト)の場合、レベルベースのログファイル命名規則(`basename_level.log`)を使用し、`CustomLevelRotatingFileHandler`を使用します。
|
|
90
|
+
- `dir_path` (`str`): ログファイルが保存されるディレクトリです(デフォルト: `./Log`)。
|
|
91
|
+
- `log_encode` (`str`): ログファイルのエンコーディングです(デフォルト: `"utf-8"`)。初期化時に、異なるエンコーディングの既存ログファイルは変換されます。
|
|
92
|
+
- `maxBytes` (`int`): ログファイルがローテーションされるまでの最大バイト数です(デフォルト: `10 * 1024 * 1024` バイト、つまり 10MB)。
|
|
93
|
+
- `backupCount` (`int`): ローテーション後に保持するバックアップログファイルの数です(デフォルト: `10`)。
|
|
94
|
+
- `showlevel` (`str`): コンソール(RichHandlerを使用)に表示するログの最小レベルです。 `"DEBUG"`, `"INFO"`(デフォルト), `"WARNING"`, `"ERROR"`, `"CRITICAL"`, `"NOTSET"` を受け付けます。大文字小文字は区別されません。
|
|
95
|
+
- `flag_unnecessary_loggers_to_error` (`bool`): `True`(デフォルト)の場合、一般的で詳細なサードパーティライブラリ(例: `werkzeug`, `urllib3`, `httpx` など)のロガーレベルを `ERROR` に設定し、コンソールのノイズを減らします。
|
|
96
|
+
|
|
97
|
+
## カスタムファイルハンドラ
|
|
98
|
+
|
|
99
|
+
このパッケージには、`logging.handlers.RotatingFileHandler` をベースにしたカスタムハンドラが含まれています:
|
|
100
|
+
|
|
101
|
+
- `NoColorFormatter`: ANSIエスケープシーケンスを取り除くロギングフォーマッターです。`RichHandler`使用時のプレーンテキストログファイルに有用です。
|
|
102
|
+
- `CustomLevelRotatingFileHandler`: サイズベースのローテーションを意図したカスタムハンドラで、`flag_datelog` が `False` の場合に使用されます。
|
|
103
|
+
- `CustomDateRotatingFileHandler`: 日付を意識した命名規則を持つサイズベースのローテーションを意図したカスタムハンドラで、`flag_datelog` が `True` の場合に使用されます。
|
|
104
|
+
**ローテーションに関する注記:** 上記で述べたように、カスタムハンドラのローテーションロジックと `CustomLogger.log_main` で生成されるファイル名との間に問題がある可能性があります。
|
|
105
|
+
|
|
106
|
+
## log_decorator
|
|
107
|
+
|
|
108
|
+
関数への入退室およびエラーを自動的に`DEBUG`レベルと`ERROR`レベルでログに記録するためのシンプルなデコレーター(`log_decorator`)が提供されています。
|
|
109
|
+
|
|
110
|
+
## ライセンス
|
|
111
|
+
|
|
112
|
+
このプロジェクトはMITライセンスの下で提供されます - 詳細については LICENSE ファイルを参照してください。
|
agesuta-0.1.0/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# agesuta
|
|
2
|
+
|
|
3
|
+
## README.txtはAIによる生成ファイルです。(README.txt is a file generated by AI.)
|
|
4
|
+
|
|
5
|
+
Python標準の`logging`モジュールをベースにしたカスタムロギングユーティリティです。`rich`による色付きコンソール出力、カスタムローテーションハンドラによるファイルロギング、ログファイルのエンコーディング処理機能を備えています。
|
|
6
|
+
|
|
7
|
+
**注記:** 現在のバージョンに含まれるカスタムファイルローテーションハンドラ(`CustomLevelRotatingFileHandler`, `CustomDateRotatingFileHandler`)は、`CustomLogger.log_main`で生成されるファイル名との間に論理的な不整合がある可能性があります。これにより、一部の設定でログファイルが正しくローテーションされない場合があります。使用には注意が必要か、ハンドラの実装を確認してください。
|
|
8
|
+
|
|
9
|
+
## インストール
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install agesuta
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 使用方法
|
|
16
|
+
|
|
17
|
+
**基本的な使用例を以下に示します:**
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import logging
|
|
21
|
+
from agesuta import CustomLogger, log_decorator
|
|
22
|
+
|
|
23
|
+
# ロガーの初期設定を行うインスタンスを作成します
|
|
24
|
+
# これにより、ルートロガーにハンドラが設定されます
|
|
25
|
+
Cl_logger = CustomLogger(
|
|
26
|
+
flag_datelog=False, # 日付ベースの単一ログファイルにする場合は True に設定
|
|
27
|
+
dir_path="./Log",
|
|
28
|
+
log_encode="utf-8",
|
|
29
|
+
showlevel="INFO", # コンソール出力の最小レベル
|
|
30
|
+
maxBytes=5 * 1024 * 1024, # ログファイルを5MBでローテーション
|
|
31
|
+
backupCount=5 # バックアップファイルを最大5世代保持
|
|
32
|
+
)
|
|
33
|
+
Cl_logger.log_main() # ロギング設定を適用します
|
|
34
|
+
|
|
35
|
+
# 標準のlogging.getLogger()を使ってロガーインスタンスを取得します
|
|
36
|
+
# これは Cl_logger.log_main() で設定されたハンドラを使用します
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
logger.debug("これはデバッグメッセージです。")
|
|
40
|
+
logger.info("これは情報メッセージです。")
|
|
41
|
+
logger.warning("これは警告メッセージです。")
|
|
42
|
+
logger.error("これはエラーメッセージです。")
|
|
43
|
+
logger.critical("これはクリティカルメッセージです。")
|
|
44
|
+
|
|
45
|
+
# log_decorator の使用例
|
|
46
|
+
# from agesuta import log_decorator # パッケージ名に合わせて変更してください
|
|
47
|
+
|
|
48
|
+
# @log_decorator(logger)
|
|
49
|
+
# def my_function(arg1, arg2):
|
|
50
|
+
# logger.info("関数の中で何か処理しています")
|
|
51
|
+
# return arg1 + arg2
|
|
52
|
+
|
|
53
|
+
# result = my_function(10, 20)
|
|
54
|
+
# print(f"関数の結果: {result}")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`flag_datelog` が `False` の場合、異なるレベル(例: `my_script_0_debug.log`, `my_script_1_info.log` など)ごとに個別のログファイルが `dir_path` に作成されます。
|
|
58
|
+
`flag_datelog` が `True` の場合、日付を含む単一のログファイル(例: `my_script_YYYY-MM-DD.log`)が作成されます。
|
|
59
|
+
|
|
60
|
+
## CustomLogger パラメータ
|
|
61
|
+
|
|
62
|
+
- `flag_datelog` (`bool`): `True`の場合、日付ベースのログファイル命名規則(`basename_YYYY-MM-DD.log`)を使用し、`CustomDateRotatingFileHandler`を使用します。`False`(デフォルト)の場合、レベルベースのログファイル命名規則(`basename_level.log`)を使用し、`CustomLevelRotatingFileHandler`を使用します。
|
|
63
|
+
- `dir_path` (`str`): ログファイルが保存されるディレクトリです(デフォルト: `./Log`)。
|
|
64
|
+
- `log_encode` (`str`): ログファイルのエンコーディングです(デフォルト: `"utf-8"`)。初期化時に、異なるエンコーディングの既存ログファイルは変換されます。
|
|
65
|
+
- `maxBytes` (`int`): ログファイルがローテーションされるまでの最大バイト数です(デフォルト: `10 * 1024 * 1024` バイト、つまり 10MB)。
|
|
66
|
+
- `backupCount` (`int`): ローテーション後に保持するバックアップログファイルの数です(デフォルト: `10`)。
|
|
67
|
+
- `showlevel` (`str`): コンソール(RichHandlerを使用)に表示するログの最小レベルです。 `"DEBUG"`, `"INFO"`(デフォルト), `"WARNING"`, `"ERROR"`, `"CRITICAL"`, `"NOTSET"` を受け付けます。大文字小文字は区別されません。
|
|
68
|
+
- `flag_unnecessary_loggers_to_error` (`bool`): `True`(デフォルト)の場合、一般的で詳細なサードパーティライブラリ(例: `werkzeug`, `urllib3`, `httpx` など)のロガーレベルを `ERROR` に設定し、コンソールのノイズを減らします。
|
|
69
|
+
|
|
70
|
+
## カスタムファイルハンドラ
|
|
71
|
+
|
|
72
|
+
このパッケージには、`logging.handlers.RotatingFileHandler` をベースにしたカスタムハンドラが含まれています:
|
|
73
|
+
|
|
74
|
+
- `NoColorFormatter`: ANSIエスケープシーケンスを取り除くロギングフォーマッターです。`RichHandler`使用時のプレーンテキストログファイルに有用です。
|
|
75
|
+
- `CustomLevelRotatingFileHandler`: サイズベースのローテーションを意図したカスタムハンドラで、`flag_datelog` が `False` の場合に使用されます。
|
|
76
|
+
- `CustomDateRotatingFileHandler`: 日付を意識した命名規則を持つサイズベースのローテーションを意図したカスタムハンドラで、`flag_datelog` が `True` の場合に使用されます。
|
|
77
|
+
**ローテーションに関する注記:** 上記で述べたように、カスタムハンドラのローテーションロジックと `CustomLogger.log_main` で生成されるファイル名との間に問題がある可能性があります。
|
|
78
|
+
|
|
79
|
+
## log_decorator
|
|
80
|
+
|
|
81
|
+
関数への入退室およびエラーを自動的に`DEBUG`レベルと`ERROR`レベルでログに記録するためのシンプルなデコレーター(`log_decorator`)が提供されています。
|
|
82
|
+
|
|
83
|
+
## ライセンス
|
|
84
|
+
|
|
85
|
+
このプロジェクトはMITライセンスの下で提供されます - 詳細については LICENSE ファイルを参照してください。
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from logging import Formatter
|
|
5
|
+
from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL, NOTSET
|
|
6
|
+
from rich.logging import RichHandler
|
|
7
|
+
from logging.handlers import RotatingFileHandler
|
|
8
|
+
from chardet.universaldetector import UniversalDetector
|
|
9
|
+
import inspect
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# ANSI エスケープシーケンスを除去する Formatter
|
|
15
|
+
class NoColorFormatter(logging.Formatter):
|
|
16
|
+
# ANSI エスケープシーケンスにマッチする正規表現
|
|
17
|
+
ANSI_ESCAPE = re.compile(r"\x1B\[[0-?]*[ -/]*[@-~]")
|
|
18
|
+
|
|
19
|
+
def format(self, record):
|
|
20
|
+
msg = super().format(record)
|
|
21
|
+
# ANSI シーケンスを空文字に置換
|
|
22
|
+
return self.ANSI_ESCAPE.sub("", msg)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CustomLevelRotatingFileHandler(RotatingFileHandler):
|
|
26
|
+
def doRollover(self):
|
|
27
|
+
"""
|
|
28
|
+
ファイル名を basename.<番号>.<timestamp>.log にしてローテート。
|
|
29
|
+
ローテートのたびに既存ファイルの番号を+1し、最新を .1.<timestamp>.log にする。
|
|
30
|
+
"""
|
|
31
|
+
if self.stream:
|
|
32
|
+
self.stream.close()
|
|
33
|
+
self.stream = None
|
|
34
|
+
|
|
35
|
+
base, ext = os.path.splitext(self.baseFilename)
|
|
36
|
+
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
37
|
+
|
|
38
|
+
log_dir = os.path.dirname(self.baseFilename)
|
|
39
|
+
base_name = os.path.basename(base)
|
|
40
|
+
|
|
41
|
+
pattern = re.compile(
|
|
42
|
+
rf"^{re.escape(base_name)}\.(\d+)\.\d{{4}}-\d{{2}}-\d{{2}}_\d{{2}}-\d{{2}}-\d{{2}}{re.escape(ext)}$"
|
|
43
|
+
)
|
|
44
|
+
files_with_index = []
|
|
45
|
+
|
|
46
|
+
for fname in os.listdir(log_dir):
|
|
47
|
+
match = pattern.match(fname)
|
|
48
|
+
if match:
|
|
49
|
+
idx = int(match.group(1))
|
|
50
|
+
files_with_index.append((idx, fname))
|
|
51
|
+
|
|
52
|
+
# 番号が大きい順にずらす
|
|
53
|
+
for idx, fname in sorted(files_with_index, reverse=True):
|
|
54
|
+
full_path = os.path.join(log_dir, fname)
|
|
55
|
+
if idx >= self.backupCount:
|
|
56
|
+
os.remove(full_path)
|
|
57
|
+
else:
|
|
58
|
+
new_name = f"{base_name}.{idx + 1}.{fname.split('.', 2)[2]}"
|
|
59
|
+
os.rename(os.path.join(log_dir, fname), os.path.join(log_dir, new_name))
|
|
60
|
+
|
|
61
|
+
# 現在のログファイルを .1.<timestamp>.log にローテート
|
|
62
|
+
dfn = os.path.join(log_dir, f"{base_name}.1.{timestamp}{ext}")
|
|
63
|
+
if os.path.exists(dfn):
|
|
64
|
+
os.remove(dfn)
|
|
65
|
+
self.rotate(self.baseFilename, dfn)
|
|
66
|
+
|
|
67
|
+
if not self.delay:
|
|
68
|
+
self.stream = self._open()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class CustomDateRotatingFileHandler(RotatingFileHandler):
|
|
72
|
+
def doRollover(self):
|
|
73
|
+
"""
|
|
74
|
+
ファイル名を basename_<timestamp>.<番号>.log にしてローテート。
|
|
75
|
+
ローテートのたびに既存ファイルの番号を+1し、最新を .1.<timestamp>.log にする。
|
|
76
|
+
"""
|
|
77
|
+
if self.stream:
|
|
78
|
+
self.stream.close()
|
|
79
|
+
self.stream = None
|
|
80
|
+
|
|
81
|
+
base, ext = os.path.splitext(self.baseFilename)
|
|
82
|
+
log_dir = os.path.dirname(self.baseFilename)
|
|
83
|
+
base_name = os.path.basename(base)
|
|
84
|
+
|
|
85
|
+
# debug.N.log にマッチするファイルを探す
|
|
86
|
+
pattern = re.compile(rf"^{re.escape(base_name)}\.(\d+){re.escape(ext)}$")
|
|
87
|
+
files_with_index = []
|
|
88
|
+
|
|
89
|
+
for fname in os.listdir(log_dir):
|
|
90
|
+
match = pattern.match(fname)
|
|
91
|
+
if match:
|
|
92
|
+
idx = int(match.group(1))
|
|
93
|
+
files_with_index.append((idx, fname))
|
|
94
|
+
|
|
95
|
+
# 番号が大きい順にずらす
|
|
96
|
+
for idx, fname in sorted(files_with_index, reverse=True):
|
|
97
|
+
full_path = os.path.join(log_dir, fname)
|
|
98
|
+
if idx >= self.backupCount:
|
|
99
|
+
os.remove(full_path)
|
|
100
|
+
else:
|
|
101
|
+
new_name = f"{base_name}.{idx + 1}{ext}"
|
|
102
|
+
os.rename(os.path.join(log_dir, fname), os.path.join(log_dir, new_name))
|
|
103
|
+
|
|
104
|
+
# 現在のログファイルを .1.log にローテート
|
|
105
|
+
dfn = os.path.join(log_dir, f"{base_name}.1{ext}")
|
|
106
|
+
if os.path.exists(dfn):
|
|
107
|
+
os.remove(dfn)
|
|
108
|
+
self.rotate(self.baseFilename, dfn)
|
|
109
|
+
|
|
110
|
+
if not self.delay:
|
|
111
|
+
self.stream = self._open()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class CustomLogger:
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
flag_datelog: bool = False,
|
|
118
|
+
dir_path: str = "./Log",
|
|
119
|
+
log_encode: str = "utf-8",
|
|
120
|
+
maxBytes: int = 10 * 1024 * 1024,
|
|
121
|
+
backupCount: int = 10,
|
|
122
|
+
showlevel="INFO",
|
|
123
|
+
flag_unnecessary_loggers_to_error=True,
|
|
124
|
+
):
|
|
125
|
+
self.flag_datelog = bool(flag_datelog)
|
|
126
|
+
self.dir_path = str(dir_path)
|
|
127
|
+
self.log_encode = str(log_encode)
|
|
128
|
+
self.maxBytes = int(maxBytes)
|
|
129
|
+
self.backupCount = int(backupCount)
|
|
130
|
+
level_dic = {
|
|
131
|
+
"DEBUG": DEBUG,
|
|
132
|
+
"INFO": INFO,
|
|
133
|
+
"WARNING": WARNING,
|
|
134
|
+
"ERROR": ERROR,
|
|
135
|
+
"CRITICAL": CRITICAL,
|
|
136
|
+
"NOTSET": NOTSET,
|
|
137
|
+
}
|
|
138
|
+
self.showlevel = level_dic.get(showlevel.upper(), INFO)
|
|
139
|
+
if flag_unnecessary_loggers_to_error:
|
|
140
|
+
logger_names = [
|
|
141
|
+
"werkzeug",
|
|
142
|
+
"httpcore.http11",
|
|
143
|
+
"urllib3.connectionpool",
|
|
144
|
+
"googleapiclient.discovery_cache",
|
|
145
|
+
"googleapiclient.discovery",
|
|
146
|
+
"selenium.webdriver.remote.remote_connection",
|
|
147
|
+
"WDM",
|
|
148
|
+
"oauthlib.oauth1.rfc5849",
|
|
149
|
+
"requests_oauthlib.oauth1_auth",
|
|
150
|
+
"httpx"
|
|
151
|
+
]
|
|
152
|
+
for name in logger_names:
|
|
153
|
+
logging.getLogger(name).setLevel(logging.ERROR)
|
|
154
|
+
|
|
155
|
+
def encode_detect(self, filepath):
|
|
156
|
+
if not os.path.exists(filepath):
|
|
157
|
+
return 1, ""
|
|
158
|
+
try:
|
|
159
|
+
try:
|
|
160
|
+
detector = UniversalDetector()
|
|
161
|
+
with open(filepath, "rb") as f:
|
|
162
|
+
while True:
|
|
163
|
+
binary = f.readline()
|
|
164
|
+
if binary == b"":
|
|
165
|
+
break
|
|
166
|
+
detector.feed(binary)
|
|
167
|
+
if detector.done:
|
|
168
|
+
break
|
|
169
|
+
finally:
|
|
170
|
+
detector.close()
|
|
171
|
+
# print("encoding:" + str(detector.result["encoding"]))
|
|
172
|
+
if detector.result["encoding"] is None:
|
|
173
|
+
return 0, "CP932"
|
|
174
|
+
elif detector.result["encoding"] in ["utf-8", "UTF-8-SIG"]:
|
|
175
|
+
return 0, "utf-8"
|
|
176
|
+
elif detector.result["encoding"] in ["CP932", "SHIFT_JIS", "Windows-1254"]:
|
|
177
|
+
return 0, "CP932"
|
|
178
|
+
else:
|
|
179
|
+
return 0, detector.result["encoding"]
|
|
180
|
+
except:
|
|
181
|
+
return 2, ""
|
|
182
|
+
|
|
183
|
+
def change_encode(self, filepath, after_encode):
|
|
184
|
+
try:
|
|
185
|
+
ret, before_encode = self.encode_detect(filepath)
|
|
186
|
+
if ret == 1:
|
|
187
|
+
# ファイルがない場合はそのまま返す
|
|
188
|
+
return 1
|
|
189
|
+
if before_encode == after_encode:
|
|
190
|
+
# 再エンコードが不要な場合はそのまま返す
|
|
191
|
+
return 0
|
|
192
|
+
try:
|
|
193
|
+
os.rename(filepath, filepath + "_bk")
|
|
194
|
+
except:
|
|
195
|
+
if os.path.exists(filepath):
|
|
196
|
+
os.remove(filepath)
|
|
197
|
+
# リネーム失敗時は削除して返す
|
|
198
|
+
return 1
|
|
199
|
+
if ret == 2:
|
|
200
|
+
# エンコードの読み取り失敗時はリネームして返す
|
|
201
|
+
return 1
|
|
202
|
+
try:
|
|
203
|
+
with open(filepath + "_bk", "r", encoding=before_encode) as f:
|
|
204
|
+
txt = f.read()
|
|
205
|
+
with open(filepath, "w", encoding=after_encode) as f:
|
|
206
|
+
f.write(txt)
|
|
207
|
+
os.remove(filepath + "_bk")
|
|
208
|
+
except:
|
|
209
|
+
if os.path.exists(filepath):
|
|
210
|
+
os.remove(filepath)
|
|
211
|
+
return 1
|
|
212
|
+
return 0
|
|
213
|
+
except:
|
|
214
|
+
return 1
|
|
215
|
+
|
|
216
|
+
def get_script_display_name(self):
|
|
217
|
+
# PyInstallerなどでexe化している場合
|
|
218
|
+
if getattr(sys, "frozen", False):
|
|
219
|
+
# sys.executable はexeのパスなので、
|
|
220
|
+
# ここでbasename (拡張子なし) を取得
|
|
221
|
+
base = os.path.basename(sys.executable)
|
|
222
|
+
name, _ = os.path.splitext(base)
|
|
223
|
+
return name + "_"
|
|
224
|
+
else:
|
|
225
|
+
# 通常のスクリプト実行の場合
|
|
226
|
+
# __main__モジュールの__file__が使えるのでそれを利用
|
|
227
|
+
try:
|
|
228
|
+
# __main__モジュールがあれば
|
|
229
|
+
import __main__
|
|
230
|
+
|
|
231
|
+
if hasattr(__main__, "__file__"):
|
|
232
|
+
base = os.path.basename(__main__.__file__)
|
|
233
|
+
name, _ = os.path.splitext(base)
|
|
234
|
+
return name + "_"
|
|
235
|
+
except Exception:
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
# もし上記で取得できなかった場合は、inspectでスタックから探す
|
|
239
|
+
for frame in inspect.stack():
|
|
240
|
+
module = inspect.getmodule(frame[0])
|
|
241
|
+
if module and hasattr(module, "__file__"):
|
|
242
|
+
base = os.path.basename(module.__file__)
|
|
243
|
+
name, _ = os.path.splitext(base)
|
|
244
|
+
return name + "_"
|
|
245
|
+
# もし何も見つからなければ空文字
|
|
246
|
+
return ""
|
|
247
|
+
|
|
248
|
+
def log_main(self):
|
|
249
|
+
handlers = []
|
|
250
|
+
importname = self.get_script_display_name()
|
|
251
|
+
|
|
252
|
+
# ストリームハンドラの設定
|
|
253
|
+
rich_handler: RichHandler = RichHandler(rich_tracebacks=True)
|
|
254
|
+
rich_handler.setLevel(self.showlevel)
|
|
255
|
+
rich_handler.setFormatter(NoColorFormatter("%(message)s"))
|
|
256
|
+
handlers.append(rich_handler)
|
|
257
|
+
|
|
258
|
+
# 保存先の有無チェック
|
|
259
|
+
if not os.path.isdir(self.dir_path):
|
|
260
|
+
os.makedirs(self.dir_path, exist_ok=True)
|
|
261
|
+
|
|
262
|
+
# ファイルハンドラの設定
|
|
263
|
+
if self.flag_datelog:
|
|
264
|
+
logfile_path = f"{self.dir_path}/{importname}{datetime.now():%Y-%m-%d}.log"
|
|
265
|
+
if os.path.exists(logfile_path):
|
|
266
|
+
ret = self.change_encode(logfile_path, self.log_encode)
|
|
267
|
+
file_handler = CustomDateRotatingFileHandler(
|
|
268
|
+
logfile_path,
|
|
269
|
+
"a",
|
|
270
|
+
maxBytes=self.maxBytes,
|
|
271
|
+
backupCount=self.backupCount,
|
|
272
|
+
encoding=self.log_encode,
|
|
273
|
+
)
|
|
274
|
+
file_handler.setLevel(DEBUG)
|
|
275
|
+
file_handler.setFormatter(
|
|
276
|
+
# Formatter("%(asctime)s [%(levelname).4s] %(filename)s %(funcName)s %(lineno)d: %(message)s")
|
|
277
|
+
NoColorFormatter(
|
|
278
|
+
"%(asctime)s [%(levelname)s] %(name)s %(filename)s %(funcName)s %(lineno)d: %(message)s"
|
|
279
|
+
)
|
|
280
|
+
)
|
|
281
|
+
handlers.append(file_handler)
|
|
282
|
+
|
|
283
|
+
# ルートロガーの設定
|
|
284
|
+
logging.basicConfig(level=NOTSET, handlers=handlers)
|
|
285
|
+
else:
|
|
286
|
+
loggger_dic = {
|
|
287
|
+
f"{importname}0_debug": DEBUG,
|
|
288
|
+
f"{importname}1_info": INFO,
|
|
289
|
+
f"{importname}2_warning": WARNING,
|
|
290
|
+
f"{importname}3_error": ERROR,
|
|
291
|
+
}
|
|
292
|
+
for i in list(loggger_dic.keys()):
|
|
293
|
+
logfile_path = f"{self.dir_path}/{i}.log"
|
|
294
|
+
if os.path.exists(logfile_path):
|
|
295
|
+
ret = self.change_encode(logfile_path, self.log_encode)
|
|
296
|
+
logger_temp = CustomLevelRotatingFileHandler(
|
|
297
|
+
logfile_path,
|
|
298
|
+
"a",
|
|
299
|
+
maxBytes=self.maxBytes,
|
|
300
|
+
backupCount=self.backupCount,
|
|
301
|
+
encoding=self.log_encode,
|
|
302
|
+
)
|
|
303
|
+
logger_temp.setLevel(loggger_dic[i])
|
|
304
|
+
logger_temp.setFormatter(
|
|
305
|
+
# Formatter("%(asctime)s [%(levelname).4s] %(filename)s %(funcName)s %(lineno)d: %(message)s")
|
|
306
|
+
NoColorFormatter(
|
|
307
|
+
"%(asctime)s [%(levelname)s] %(name)s %(filename)s %(funcName)s %(lineno)d: %(message)s"
|
|
308
|
+
)
|
|
309
|
+
)
|
|
310
|
+
handlers.append(logger_temp)
|
|
311
|
+
|
|
312
|
+
# ルートロガーの設定
|
|
313
|
+
logging.basicConfig(level=NOTSET, handlers=handlers)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def log_decorator(logger):
|
|
317
|
+
def _log_decorator(func):
|
|
318
|
+
def wrapper(*args, **kwargs):
|
|
319
|
+
local_args = locals()
|
|
320
|
+
try:
|
|
321
|
+
logger.debug(f"start: {func.__name__} args: {str(local_args)}")
|
|
322
|
+
return_val = func(*args, **kwargs)
|
|
323
|
+
logger.debug(f" end: {func.__name__} ret: {str(return_val)}")
|
|
324
|
+
# logger.debug(f" end: {func.__name__}")
|
|
325
|
+
return return_val
|
|
326
|
+
except Exception as e:
|
|
327
|
+
logger.error(f"error: {func.__name__}")
|
|
328
|
+
raise e
|
|
329
|
+
|
|
330
|
+
return wrapper
|
|
331
|
+
|
|
332
|
+
return _log_decorator
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
if __name__ == "__main__":
|
|
336
|
+
Cl_logger = CustomLogger(flag_datelog=False, dir_path="./Log", log_encode="utf-8")
|
|
337
|
+
Cl_logger.log_main()
|
|
338
|
+
logger = logging.getLogger(__name__)
|
|
339
|
+
logger.debug("テスト: debug")
|
|
340
|
+
logger.info("テスト: info")
|
|
341
|
+
logger.warning("テスト: warning")
|
|
342
|
+
logger.error("テスト: error")
|
|
343
|
+
logger.critical("テスト: critical")
|
|
344
|
+
os.system("PAUSE")
|