simple-channel-log 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.
- simple_channel_log-1.0/LICENSE +21 -0
- simple_channel_log-1.0/PKG-INFO +142 -0
- simple_channel_log-1.0/README.md +89 -0
- simple_channel_log-1.0/setup.cfg +4 -0
- simple_channel_log-1.0/setup.py +58 -0
- simple_channel_log-1.0/simple_channel_log/__init__.py +71 -0
- simple_channel_log-1.0/simple_channel_log/i simple_channel_log.py +555 -0
- simple_channel_log-1.0/simple_channel_log.egg-info/PKG-INFO +142 -0
- simple_channel_log-1.0/simple_channel_log.egg-info/SOURCES.txt +10 -0
- simple_channel_log-1.0/simple_channel_log.egg-info/dependency_links.txt +1 -0
- simple_channel_log-1.0/simple_channel_log.egg-info/requires.txt +7 -0
- simple_channel_log-1.0/simple_channel_log.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Unnamed great master <gqylpy@outlook.com>.
|
|
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.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: simple-channel-log
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: 轻量高效的日志库,支持多级别日志记录、日志轮转、流水日志追踪及埋点日志功能,深度集成 Flask 和 Requests 框架。
|
|
5
|
+
Author: Unnamed great master
|
|
6
|
+
Author-email: <gqylpy@outlook.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Source, https://github.com/2018-11-27/simple-channel-log
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
|
+
Classifier: Natural Language :: Chinese (Simplified)
|
|
13
|
+
Classifier: Natural Language :: English
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Classifier: Topic :: Artistic Software
|
|
17
|
+
Classifier: Topic :: Internet :: Log Analysis
|
|
18
|
+
Classifier: Topic :: Text Processing
|
|
19
|
+
Classifier: Programming Language :: Python :: 2.7
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.0
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.1
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.2
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.3
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.4
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.5
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
28
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
29
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
34
|
+
Requires-Python: >=2.7
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
License-File: LICENSE
|
|
37
|
+
Requires-Dist: gqylpy_log==0.0a1
|
|
38
|
+
Provides-Extra: flask
|
|
39
|
+
Requires-Dist: Flask>=0.10; extra == "flask"
|
|
40
|
+
Provides-Extra: requests
|
|
41
|
+
Requires-Dist: requests>=2.19; extra == "requests"
|
|
42
|
+
Dynamic: author
|
|
43
|
+
Dynamic: author-email
|
|
44
|
+
Dynamic: classifier
|
|
45
|
+
Dynamic: description
|
|
46
|
+
Dynamic: description-content-type
|
|
47
|
+
Dynamic: license
|
|
48
|
+
Dynamic: project-url
|
|
49
|
+
Dynamic: provides-extra
|
|
50
|
+
Dynamic: requires-dist
|
|
51
|
+
Dynamic: requires-python
|
|
52
|
+
Dynamic: summary
|
|
53
|
+
|
|
54
|
+
# simple-channel-log
|
|
55
|
+
|
|
56
|
+
[](https://github.com/2018-11-27/simple-channel-log/releases/latest)
|
|
57
|
+
[](https://github.com/2018-11-27/simple-channel-log)
|
|
58
|
+
[](https://opensource.org/licenses/MIT)
|
|
59
|
+
[](https://pepy.tech/project/simple-channel-log)
|
|
60
|
+
|
|
61
|
+
轻量高效的日志库,支持多级别日志记录、日志轮转、流水日志追踪及埋点日志功能,深度集成 Flask 和 Requests 框架。
|
|
62
|
+
|
|
63
|
+
## 主要特性
|
|
64
|
+
|
|
65
|
+
- 📅 支持按时间轮转日志(天/小时/分钟等)
|
|
66
|
+
- 📊 提供多级别日志记录(DEBUG/INFO/WARNING/ERROR/CRITICAL)
|
|
67
|
+
- 🌐 内置Flask中间件记录请求/响应流水日志(Journallog-in)
|
|
68
|
+
- 📡 集成Requests会话记录外部调用流水日志(Journallog-out)
|
|
69
|
+
- 🔍 智能处理长字符串截断(超过1000字符自动标记)
|
|
70
|
+
- 📁 自动创建分级日志目录结构
|
|
71
|
+
- 💻 支持终端输出与文件存储分离控制
|
|
72
|
+
|
|
73
|
+
## 安装
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install simple_channel_log
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 快速入门
|
|
80
|
+
|
|
81
|
+
### 基础配置
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
# coding:utf-8
|
|
85
|
+
import simple_channel_log as log
|
|
86
|
+
|
|
87
|
+
# 初始化日志配置
|
|
88
|
+
log.__init__("<your_appname>", "<your_syscode>", logdir="/app/logs")
|
|
89
|
+
|
|
90
|
+
# 初始化后可直接调用日志方法,日志将记录到参数 `logdir` 指定的目录中
|
|
91
|
+
log.debug("调试信息", extra_field="value")
|
|
92
|
+
log.info("业务日志", user_id=123)
|
|
93
|
+
log.warning("异常预警", error_code=500)
|
|
94
|
+
log.error("系统错误", stack_trace="...")
|
|
95
|
+
|
|
96
|
+
# 埋点日志
|
|
97
|
+
log.trace(
|
|
98
|
+
user_id=123,
|
|
99
|
+
action="purchase",
|
|
100
|
+
item_count=2
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Flask 流水日志
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# coding:utf-8
|
|
108
|
+
import simple_channel_log as log
|
|
109
|
+
|
|
110
|
+
# 初始化日志配置
|
|
111
|
+
log.__init__(..., enable_journallog_in=True)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
设置 `enable_journallog_in=True` 表示启用 Flask 流水日志,将自动记录每个接口的调用信息。
|
|
115
|
+
|
|
116
|
+
### Requests 外部调用追踪
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
# coding:utf-8
|
|
120
|
+
import simple_channel_log as log
|
|
121
|
+
|
|
122
|
+
# 初始化日志配置
|
|
123
|
+
log.__init__(..., enable_journallog_out=True)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
设置 `enable_journallog_out=True` 表示启用 Requests 外部调用追踪,将自动记录每个请求的调用信息。
|
|
127
|
+
|
|
128
|
+
## 详细配置
|
|
129
|
+
|
|
130
|
+
### 初始化参数
|
|
131
|
+
|
|
132
|
+
| 参数名 | 类型 | 默认值 | 说明 |
|
|
133
|
+
|-----------------------|------|----------|-------------------------------|
|
|
134
|
+
| appname | str | 必填 | 应用名称,建议与CI配置一致 |
|
|
135
|
+
| syscode | str | 必填 | 系统编码(大写) |
|
|
136
|
+
| logdir | str | 系统相关默认路径 | 日志存储根目录 |
|
|
137
|
+
| when | str | 'D' | 轮转周期:W(周)/D(天)/H(时)/M(分)/S(秒) |
|
|
138
|
+
| interval | int | 1 | 轮转频率 |
|
|
139
|
+
| backup_count | int | 7 | 历史日志保留数量(0=永久) |
|
|
140
|
+
| output_to_terminal | bool | False | 启用后日志将同时输出到控制台 |
|
|
141
|
+
| enable_journallog_in | bool | False | 启用Flask请求流水日志 |
|
|
142
|
+
| enable_journallog_out | bool | False | 启用Requests外部调用日志 |
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# simple-channel-log
|
|
2
|
+
|
|
3
|
+
[](https://github.com/2018-11-27/simple-channel-log/releases/latest)
|
|
4
|
+
[](https://github.com/2018-11-27/simple-channel-log)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://pepy.tech/project/simple-channel-log)
|
|
7
|
+
|
|
8
|
+
轻量高效的日志库,支持多级别日志记录、日志轮转、流水日志追踪及埋点日志功能,深度集成 Flask 和 Requests 框架。
|
|
9
|
+
|
|
10
|
+
## 主要特性
|
|
11
|
+
|
|
12
|
+
- 📅 支持按时间轮转日志(天/小时/分钟等)
|
|
13
|
+
- 📊 提供多级别日志记录(DEBUG/INFO/WARNING/ERROR/CRITICAL)
|
|
14
|
+
- 🌐 内置Flask中间件记录请求/响应流水日志(Journallog-in)
|
|
15
|
+
- 📡 集成Requests会话记录外部调用流水日志(Journallog-out)
|
|
16
|
+
- 🔍 智能处理长字符串截断(超过1000字符自动标记)
|
|
17
|
+
- 📁 自动创建分级日志目录结构
|
|
18
|
+
- 💻 支持终端输出与文件存储分离控制
|
|
19
|
+
|
|
20
|
+
## 安装
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install simple_channel_log
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 快速入门
|
|
27
|
+
|
|
28
|
+
### 基础配置
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
# coding:utf-8
|
|
32
|
+
import simple_channel_log as log
|
|
33
|
+
|
|
34
|
+
# 初始化日志配置
|
|
35
|
+
log.__init__("<your_appname>", "<your_syscode>", logdir="/app/logs")
|
|
36
|
+
|
|
37
|
+
# 初始化后可直接调用日志方法,日志将记录到参数 `logdir` 指定的目录中
|
|
38
|
+
log.debug("调试信息", extra_field="value")
|
|
39
|
+
log.info("业务日志", user_id=123)
|
|
40
|
+
log.warning("异常预警", error_code=500)
|
|
41
|
+
log.error("系统错误", stack_trace="...")
|
|
42
|
+
|
|
43
|
+
# 埋点日志
|
|
44
|
+
log.trace(
|
|
45
|
+
user_id=123,
|
|
46
|
+
action="purchase",
|
|
47
|
+
item_count=2
|
|
48
|
+
)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Flask 流水日志
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
# coding:utf-8
|
|
55
|
+
import simple_channel_log as log
|
|
56
|
+
|
|
57
|
+
# 初始化日志配置
|
|
58
|
+
log.__init__(..., enable_journallog_in=True)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
设置 `enable_journallog_in=True` 表示启用 Flask 流水日志,将自动记录每个接口的调用信息。
|
|
62
|
+
|
|
63
|
+
### Requests 外部调用追踪
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
# coding:utf-8
|
|
67
|
+
import simple_channel_log as log
|
|
68
|
+
|
|
69
|
+
# 初始化日志配置
|
|
70
|
+
log.__init__(..., enable_journallog_out=True)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
设置 `enable_journallog_out=True` 表示启用 Requests 外部调用追踪,将自动记录每个请求的调用信息。
|
|
74
|
+
|
|
75
|
+
## 详细配置
|
|
76
|
+
|
|
77
|
+
### 初始化参数
|
|
78
|
+
|
|
79
|
+
| 参数名 | 类型 | 默认值 | 说明 |
|
|
80
|
+
|-----------------------|------|----------|-------------------------------|
|
|
81
|
+
| appname | str | 必填 | 应用名称,建议与CI配置一致 |
|
|
82
|
+
| syscode | str | 必填 | 系统编码(大写) |
|
|
83
|
+
| logdir | str | 系统相关默认路径 | 日志存储根目录 |
|
|
84
|
+
| when | str | 'D' | 轮转周期:W(周)/D(天)/H(时)/M(分)/S(秒) |
|
|
85
|
+
| interval | int | 1 | 轮转频率 |
|
|
86
|
+
| backup_count | int | 7 | 历史日志保留数量(0=永久) |
|
|
87
|
+
| output_to_terminal | bool | False | 启用后日志将同时输出到控制台 |
|
|
88
|
+
| enable_journallog_in | bool | False | 启用Flask请求流水日志 |
|
|
89
|
+
| enable_journallog_out | bool | False | 启用Requests外部调用日志 |
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# coding:utf-8
|
|
2
|
+
import sys
|
|
3
|
+
import codecs
|
|
4
|
+
import setuptools
|
|
5
|
+
|
|
6
|
+
if sys.version_info.major < 3:
|
|
7
|
+
open = codecs.open
|
|
8
|
+
|
|
9
|
+
setuptools.setup(
|
|
10
|
+
name='simple-channel-log',
|
|
11
|
+
version='1.0',
|
|
12
|
+
author='Unnamed great master',
|
|
13
|
+
author_email='<gqylpy@outlook.com>',
|
|
14
|
+
license='MIT',
|
|
15
|
+
project_urls={
|
|
16
|
+
'Source': 'https://github.com/2018-11-27/simple-channel-log'
|
|
17
|
+
},
|
|
18
|
+
description='''
|
|
19
|
+
轻量高效的日志库,支持多级别日志记录、日志轮转、流水日志追踪及埋点日志功能,深度集成
|
|
20
|
+
Flask 和 Requests 框架。
|
|
21
|
+
'''.strip().replace('\n ', ''),
|
|
22
|
+
long_description=open('README.md', encoding='utf8').read(),
|
|
23
|
+
long_description_content_type='text/markdown',
|
|
24
|
+
packages=['simple_channel_log'],
|
|
25
|
+
python_requires='>=2.7',
|
|
26
|
+
install_requires=['gqylpy_log==0.0a1'],
|
|
27
|
+
extras_require={
|
|
28
|
+
'flask': ['Flask>=0.10'],
|
|
29
|
+
'requests': ['requests>=2.19']
|
|
30
|
+
},
|
|
31
|
+
classifiers=[
|
|
32
|
+
'Development Status :: 3 - Alpha',
|
|
33
|
+
'Intended Audience :: Developers',
|
|
34
|
+
'License :: OSI Approved :: Apache Software License',
|
|
35
|
+
'Natural Language :: Chinese (Simplified)',
|
|
36
|
+
'Natural Language :: English',
|
|
37
|
+
'Operating System :: OS Independent',
|
|
38
|
+
'Topic :: Software Development :: Libraries :: Python Modules',
|
|
39
|
+
'Topic :: Artistic Software',
|
|
40
|
+
'Topic :: Internet :: Log Analysis',
|
|
41
|
+
'Topic :: Text Processing',
|
|
42
|
+
'Programming Language :: Python :: 2.7',
|
|
43
|
+
'Programming Language :: Python :: 3.0',
|
|
44
|
+
'Programming Language :: Python :: 3.1',
|
|
45
|
+
'Programming Language :: Python :: 3.2',
|
|
46
|
+
'Programming Language :: Python :: 3.3',
|
|
47
|
+
'Programming Language :: Python :: 3.4',
|
|
48
|
+
'Programming Language :: Python :: 3.5',
|
|
49
|
+
'Programming Language :: Python :: 3.6',
|
|
50
|
+
'Programming Language :: Python :: 3.7',
|
|
51
|
+
'Programming Language :: Python :: 3.8',
|
|
52
|
+
'Programming Language :: Python :: 3.9',
|
|
53
|
+
'Programming Language :: Python :: 3.10',
|
|
54
|
+
'Programming Language :: Python :: 3.11',
|
|
55
|
+
'Programming Language :: Python :: 3.12',
|
|
56
|
+
'Programming Language :: Python :: 3.13'
|
|
57
|
+
]
|
|
58
|
+
)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# coding:utf-8
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def __init__(
|
|
5
|
+
appname, # type: str
|
|
6
|
+
syscode, # type: str
|
|
7
|
+
logdir =None, # type: str
|
|
8
|
+
when =None, # type: str
|
|
9
|
+
interval =None, # type: int
|
|
10
|
+
backup_count =None, # type: int
|
|
11
|
+
output_to_terminal =None, # type: bool
|
|
12
|
+
enable_journallog_in =None, # type: bool
|
|
13
|
+
enable_journallog_out=None # type: bool
|
|
14
|
+
):
|
|
15
|
+
"""
|
|
16
|
+
初始化日志配置。
|
|
17
|
+
|
|
18
|
+
@param appname:
|
|
19
|
+
你的应用名称,通常与 GitLab CI 文件中的 APPNAME 字段一致。
|
|
20
|
+
@param syscode:
|
|
21
|
+
你的应用分配的系统编码(大写),应与 GitLab CI 文件中的 SVCCODE 字段一致。
|
|
22
|
+
@param logdir:
|
|
23
|
+
指定日志目录,默认为 "/app/logs/<syscode>_<appname>"(如果你的系统是 Windows
|
|
24
|
+
则为 "C:\\BllLogs\\<syscode>_<appname>")。
|
|
25
|
+
@param when:
|
|
26
|
+
控制日志轮转周期,默认为 "D"。支持按天/小时/分钟等单位滚动。可选值有:W:周, D:天,
|
|
27
|
+
H:小时, M:分钟, S:秒, 等等。
|
|
28
|
+
@param interval:
|
|
29
|
+
日志轮转频率,默认为 1 。同参数 `when` 一起使用(如:`when="D"` 且
|
|
30
|
+
`interval=1` 表示每天滚动一次)。
|
|
31
|
+
@param backup_count:
|
|
32
|
+
日志保留策略,控制最大历史版本数量,默认为 7。设为 0 则永久保留。
|
|
33
|
+
@param output_to_terminal:
|
|
34
|
+
设为 True 日志将同时输出到终端,默认为 False。流水日志和埋点日志除外。
|
|
35
|
+
@param enable_journallog_in:
|
|
36
|
+
设为 True 表示启用内部流水日志,默认为 False。目前仅支持 Flask 框架。
|
|
37
|
+
@param enable_journallog_out:
|
|
38
|
+
设为 True 表示启用外部流水日志,默认为 False。目前仅支持 requests 框架。
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def debug (msg, *args, **extra): pass
|
|
43
|
+
def info (msg, *args, **extra): pass
|
|
44
|
+
def warning (msg, *args, **extra): pass
|
|
45
|
+
def warn (msg, *args, **extra): pass
|
|
46
|
+
def error (msg, *args, **extra): pass
|
|
47
|
+
def exception(msg, *args, **extra): pass
|
|
48
|
+
def critical (msg, *args, **extra): pass
|
|
49
|
+
def fatal (msg, *args, **extra): pass
|
|
50
|
+
|
|
51
|
+
def trace(**extra): pass # 埋点日志
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class _xe6_xad_x8c_xe7_x90_xaa_xe6_x80_xa1_xe7_x8e_xb2_xe8_x90_x8d_xe4_xba_x91:
|
|
55
|
+
import sys
|
|
56
|
+
|
|
57
|
+
ipath = __name__ + '.i ' + __name__
|
|
58
|
+
__import__(ipath)
|
|
59
|
+
|
|
60
|
+
ipack = sys.modules[__name__]
|
|
61
|
+
icode = globals()['i ' + __name__]
|
|
62
|
+
|
|
63
|
+
for iname in globals():
|
|
64
|
+
if iname[0] != '_':
|
|
65
|
+
ifunc = getattr(icode, iname, None)
|
|
66
|
+
if ifunc:
|
|
67
|
+
ifunc.__module__ = __package__
|
|
68
|
+
ifunc.__doc__ = getattr(ipack, iname).__doc__
|
|
69
|
+
setattr(ipack, iname, ifunc)
|
|
70
|
+
|
|
71
|
+
ipack.__init__ = icode.__init__
|
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
# coding:utf-8
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
import uuid
|
|
7
|
+
import json as jsonx
|
|
8
|
+
import socket
|
|
9
|
+
import inspect
|
|
10
|
+
import warnings
|
|
11
|
+
import functools
|
|
12
|
+
import threading
|
|
13
|
+
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
|
|
16
|
+
if os.path.basename(sys.argv[0]) != 'setup.py':
|
|
17
|
+
import gqylpy_log as glog
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from flask import Flask
|
|
21
|
+
except ImportError:
|
|
22
|
+
Flask = None
|
|
23
|
+
else:
|
|
24
|
+
from flask import g
|
|
25
|
+
from flask import request
|
|
26
|
+
from flask import current_app
|
|
27
|
+
from flask import has_request_context
|
|
28
|
+
|
|
29
|
+
def __new__(cls, *a, **kw):
|
|
30
|
+
app = super(Flask, cls).__new__(cls)
|
|
31
|
+
cls.__apps__.append(app)
|
|
32
|
+
return app
|
|
33
|
+
|
|
34
|
+
if sys.version_info.major < 3:
|
|
35
|
+
__new__ = staticmethod(__new__)
|
|
36
|
+
|
|
37
|
+
Flask.__apps__, Flask.__new__ = [], __new__
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
import requests
|
|
41
|
+
except ImportError:
|
|
42
|
+
requests = None
|
|
43
|
+
|
|
44
|
+
if sys.version_info.major < 3:
|
|
45
|
+
from urlparse import urlparse
|
|
46
|
+
from urlparse import parse_qs
|
|
47
|
+
else:
|
|
48
|
+
from urllib.parse import urlparse
|
|
49
|
+
from urllib.parse import parse_qs
|
|
50
|
+
|
|
51
|
+
unicode = str
|
|
52
|
+
|
|
53
|
+
co_qualname = 'co_qualname' if sys.version_info >= (3, 11) else 'co_name'
|
|
54
|
+
|
|
55
|
+
that = sys.modules[__package__]
|
|
56
|
+
this = sys.modules[__name__]
|
|
57
|
+
|
|
58
|
+
unique = object()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
appname,
|
|
63
|
+
syscode,
|
|
64
|
+
logdir=r'C:\BllLogs' if sys.platform == 'win32' else '/app/logs',
|
|
65
|
+
when='D',
|
|
66
|
+
interval=1,
|
|
67
|
+
backup_count=7,
|
|
68
|
+
stream=unique,
|
|
69
|
+
output_to_terminal=None,
|
|
70
|
+
enable_journallog_in=False,
|
|
71
|
+
enable_journallog_out=False
|
|
72
|
+
):
|
|
73
|
+
if hasattr(this, 'appname'):
|
|
74
|
+
raise RuntimeError('repeat initialization.')
|
|
75
|
+
|
|
76
|
+
if re.match(r'[A-Za-z]\d{9}$', syscode) is None:
|
|
77
|
+
raise ValueError('parameter syscode "%s" is illegal' % syscode)
|
|
78
|
+
|
|
79
|
+
prefix = re.match(r'[a-zA-Z]\d{9}[_-]', appname)
|
|
80
|
+
if prefix is None:
|
|
81
|
+
appname = syscode.lower() + '_' + appname
|
|
82
|
+
elif prefix.group()[:-1].upper() != syscode.upper():
|
|
83
|
+
raise ValueError('parameter appname "%s" is illegal' % syscode)
|
|
84
|
+
|
|
85
|
+
if stream is not unique:
|
|
86
|
+
warnings.warn(
|
|
87
|
+
'parameter "stream" will be deprecated soon, replaced to '
|
|
88
|
+
'"output_to_terminal".', category=DeprecationWarning,
|
|
89
|
+
stacklevel=2
|
|
90
|
+
)
|
|
91
|
+
if output_to_terminal is None:
|
|
92
|
+
output_to_terminal = stream
|
|
93
|
+
|
|
94
|
+
that.appname = this.appname = appname = \
|
|
95
|
+
appname[0].lower() + appname[1:10] + '_' + appname[11:]
|
|
96
|
+
that.syscode = this.syscode = syscode.upper()
|
|
97
|
+
this.output_to_terminal = output_to_terminal
|
|
98
|
+
|
|
99
|
+
handlers = [{
|
|
100
|
+
'name': 'TimedRotatingFileHandler',
|
|
101
|
+
'level': 'DEBUG',
|
|
102
|
+
'filename': '%s/%s/debug/%s_code-debug.log' %
|
|
103
|
+
(logdir, appname, appname),
|
|
104
|
+
'encoding': 'UTF-8',
|
|
105
|
+
'when': when,
|
|
106
|
+
'interval': interval,
|
|
107
|
+
'backupCount': backup_count,
|
|
108
|
+
'options': {'onlyRecordCurrentLevel': True}
|
|
109
|
+
}]
|
|
110
|
+
|
|
111
|
+
for level in 'info', 'warning', 'error', 'critical':
|
|
112
|
+
handlers.append({
|
|
113
|
+
'name': 'TimedRotatingFileHandler',
|
|
114
|
+
'level': level.upper(),
|
|
115
|
+
'filename': '%s/%s/%s_code-%s.log' %
|
|
116
|
+
(logdir, appname, appname, level),
|
|
117
|
+
'encoding': 'UTF-8',
|
|
118
|
+
'when': when,
|
|
119
|
+
'interval': interval,
|
|
120
|
+
'backupCount': backup_count,
|
|
121
|
+
'options': {'onlyRecordCurrentLevel': True}
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
glog.__init__('code', handlers=handlers, gname='code')
|
|
125
|
+
|
|
126
|
+
if output_to_terminal:
|
|
127
|
+
glog.__init__(
|
|
128
|
+
'stream',
|
|
129
|
+
formatter={
|
|
130
|
+
'fmt': '[%(asctime)s] [%(levelname)s] %(message)s',
|
|
131
|
+
'datefmt': '%Y-%m-%d %H:%M:%S'
|
|
132
|
+
},
|
|
133
|
+
handlers=[{'name': 'StreamHandler'}],
|
|
134
|
+
gname='stream'
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
enable_journallog = False
|
|
138
|
+
|
|
139
|
+
if enable_journallog_in and Flask is not None:
|
|
140
|
+
enable_journallog = True
|
|
141
|
+
thread = threading.Thread(target=register_flask_middleware)
|
|
142
|
+
thread.daemon = True
|
|
143
|
+
thread.start()
|
|
144
|
+
|
|
145
|
+
if enable_journallog_out and requests is not None:
|
|
146
|
+
enable_journallog = True
|
|
147
|
+
requests.Session.request = journallog_out(requests.Session.request)
|
|
148
|
+
|
|
149
|
+
if enable_journallog:
|
|
150
|
+
glog.__init__(
|
|
151
|
+
'info',
|
|
152
|
+
handlers=[{
|
|
153
|
+
'name': 'TimedRotatingFileHandler',
|
|
154
|
+
'level': 'INFO',
|
|
155
|
+
'filename': '%s/%s/%s_info-info.log' %
|
|
156
|
+
(logdir, appname, appname),
|
|
157
|
+
'encoding': 'UTF-8',
|
|
158
|
+
'when': when,
|
|
159
|
+
'interval': interval,
|
|
160
|
+
'backupCount': backup_count,
|
|
161
|
+
}],
|
|
162
|
+
gname='info_'
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
glog.__init__(
|
|
166
|
+
'trace',
|
|
167
|
+
handlers=[{
|
|
168
|
+
'name': 'TimedRotatingFileHandler',
|
|
169
|
+
'level': 'DEBUG',
|
|
170
|
+
'filename': '%s/%s/trace/%s_trace-trace.log' %
|
|
171
|
+
(logdir, appname, appname),
|
|
172
|
+
'encoding': 'UTF-8',
|
|
173
|
+
'when': when,
|
|
174
|
+
'interval': interval,
|
|
175
|
+
'backupCount': backup_count,
|
|
176
|
+
}],
|
|
177
|
+
gname='trace'
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def register_flask_middleware():
|
|
182
|
+
start = time.time()
|
|
183
|
+
while not Flask.__apps__ and time.time() - start < 300:
|
|
184
|
+
time.sleep(.02)
|
|
185
|
+
|
|
186
|
+
for app in Flask.__apps__:
|
|
187
|
+
app.before_request(journallog_in_before)
|
|
188
|
+
app.after_request(journallog_in)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def logger(msg, *args, **extra):
|
|
192
|
+
args = tuple(OmitLongString(v) for v in args)
|
|
193
|
+
extra = OmitLongString(extra)
|
|
194
|
+
|
|
195
|
+
if sys.version_info.major < 3 and isinstance(msg, str):
|
|
196
|
+
msg = msg.decode('utf8')
|
|
197
|
+
|
|
198
|
+
if isinstance(msg, unicode):
|
|
199
|
+
msg = (msg % args)[:1000]
|
|
200
|
+
elif isinstance(msg, (dict, list, tuple)):
|
|
201
|
+
msg = OmitLongString(msg)
|
|
202
|
+
|
|
203
|
+
transaction_id = g.transaction_id \
|
|
204
|
+
if Flask is not None and has_request_context() else uuid.uuid4().hex
|
|
205
|
+
|
|
206
|
+
f_back = inspect.currentframe().f_back
|
|
207
|
+
level = f_back.f_code.co_name
|
|
208
|
+
f_back = f_back.f_back
|
|
209
|
+
|
|
210
|
+
data = {
|
|
211
|
+
'app_name': this.appname + '_code',
|
|
212
|
+
'level': level.upper(),
|
|
213
|
+
'log_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3],
|
|
214
|
+
'logger': __package__,
|
|
215
|
+
'thread': threading.current_thread().ident,
|
|
216
|
+
'code_message': msg,
|
|
217
|
+
'transaction_id': transaction_id,
|
|
218
|
+
'method_code': None,
|
|
219
|
+
'method_name': getattr(f_back.f_code, co_qualname),
|
|
220
|
+
'error_code': None,
|
|
221
|
+
'tag': None,
|
|
222
|
+
'host_name': socket.gethostname(),
|
|
223
|
+
'filename': f_back.f_code.co_filename,
|
|
224
|
+
'line': f_back.f_lineno
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
for k, v in extra.items():
|
|
228
|
+
if data.get(k) is None:
|
|
229
|
+
data[k] = v
|
|
230
|
+
|
|
231
|
+
getattr(glog, level)(jsonx.dumps(data, ensure_ascii=False), gname='code')
|
|
232
|
+
|
|
233
|
+
if this.output_to_terminal:
|
|
234
|
+
getattr(glog, level)(msg, gname='stream')
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def debug(msg, *args, **extra):
|
|
238
|
+
logger(msg, *args, **extra)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def info(msg, *args, **extra):
|
|
242
|
+
logger(msg, *args, **extra)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def warning(msg, *args, **extra):
|
|
246
|
+
logger(msg, *args, **extra)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
warn = warning
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def error(msg, *args, **extra):
|
|
253
|
+
logger(msg, *args, **extra)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
exception = error
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def critical(msg, *args, **extra):
|
|
260
|
+
logger(msg, *args, **extra)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
fatal = critical
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def trace(**extra):
|
|
267
|
+
extra = OmitLongString(extra)
|
|
268
|
+
extra.update({'app_name': this.appname + '_trace', 'level': 'TRACE'})
|
|
269
|
+
glog.debug(jsonx.dumps(extra, ensure_ascii=False), gname='trace')
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def journallog_in_before():
|
|
273
|
+
if request.path == '/healthcheck':
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
g.request_time = datetime.now()
|
|
277
|
+
|
|
278
|
+
if request.args:
|
|
279
|
+
request_data = request.args.to_dict()
|
|
280
|
+
elif request.form:
|
|
281
|
+
request_data = request.form.to_dict()
|
|
282
|
+
else:
|
|
283
|
+
request_body = request.data
|
|
284
|
+
try:
|
|
285
|
+
request_data = jsonx.loads(request_body) if request_body else None
|
|
286
|
+
except ValueError:
|
|
287
|
+
request_data = None
|
|
288
|
+
|
|
289
|
+
g.request_data = request_data
|
|
290
|
+
|
|
291
|
+
g.transaction_id = (
|
|
292
|
+
FindTransactionID(dict(request.headers)).result or
|
|
293
|
+
FindTransactionID(request_data).result or
|
|
294
|
+
uuid.uuid4().hex
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def journallog_in(response):
|
|
299
|
+
if request.path == '/healthcheck':
|
|
300
|
+
return response
|
|
301
|
+
|
|
302
|
+
parsed_url = urlparse(request.url)
|
|
303
|
+
address = parsed_url.scheme + '://' + parsed_url.netloc + parsed_url.path
|
|
304
|
+
|
|
305
|
+
view_func = current_app.view_functions.get(request.endpoint)
|
|
306
|
+
method_name = view_func.__name__ if view_func else None
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
response_data = jsonx.loads(response.get_data())
|
|
310
|
+
except ValueError:
|
|
311
|
+
response_data = response_code = order_id = \
|
|
312
|
+
province_code = city_code = \
|
|
313
|
+
account_type = account_num = \
|
|
314
|
+
response_account_type = response_account_num = None
|
|
315
|
+
else:
|
|
316
|
+
if isinstance(response_data, dict):
|
|
317
|
+
head = response_data.get('head')
|
|
318
|
+
x = head if isinstance(head, dict) else response_data
|
|
319
|
+
response_code = x.get('code')
|
|
320
|
+
order_id = x.get('order_id')
|
|
321
|
+
province_code = x.get('province_code')
|
|
322
|
+
city_code = x.get('city_code')
|
|
323
|
+
account_type = x.get('account_type')
|
|
324
|
+
account_num = x.get('account_num')
|
|
325
|
+
response_account_type = x.get('response_account_type')
|
|
326
|
+
response_account_num = x.get('response_account_num')
|
|
327
|
+
else:
|
|
328
|
+
response_code = order_id = \
|
|
329
|
+
province_code = city_code = \
|
|
330
|
+
account_type = account_num = \
|
|
331
|
+
response_account_type = response_account_num = None
|
|
332
|
+
|
|
333
|
+
response_time = datetime.now()
|
|
334
|
+
response_time_str = response_time.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
|
|
335
|
+
|
|
336
|
+
total_time = round((response_time - g.request_time).total_seconds(), 3)
|
|
337
|
+
|
|
338
|
+
glog.info(jsonx.dumps({
|
|
339
|
+
'app_name': this.appname + '_info',
|
|
340
|
+
'level': 'INFO',
|
|
341
|
+
'log_time': response_time_str,
|
|
342
|
+
'logger': __package__,
|
|
343
|
+
'thread': threading.current_thread().ident,
|
|
344
|
+
'transaction_id': g.transaction_id,
|
|
345
|
+
'dialog_type': 'in',
|
|
346
|
+
'address': address,
|
|
347
|
+
'fcode': request.headers.get('User-Agent'),
|
|
348
|
+
'tcode': this.syscode,
|
|
349
|
+
'method_code': None,
|
|
350
|
+
'method_name': method_name,
|
|
351
|
+
'http_method': request.method,
|
|
352
|
+
'request_time': g.request_time.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3],
|
|
353
|
+
'request_headers': dict(request.headers),
|
|
354
|
+
'request_payload': OmitLongString(g.request_data),
|
|
355
|
+
'response_time': response_time_str,
|
|
356
|
+
'response_headers': dict(response.headers),
|
|
357
|
+
'response_payload': OmitLongString(response_data),
|
|
358
|
+
'response_code': response_code,
|
|
359
|
+
'response_remark': None,
|
|
360
|
+
'http_status_code': response.status_code,
|
|
361
|
+
'order_id': order_id,
|
|
362
|
+
'province_code': province_code,
|
|
363
|
+
'city_code': city_code,
|
|
364
|
+
'total_time': total_time,
|
|
365
|
+
'error_code': response_code,
|
|
366
|
+
'request_ip': request.remote_addr,
|
|
367
|
+
'host_ip': parsed_url.hostname,
|
|
368
|
+
'host_name': socket.gethostname(),
|
|
369
|
+
'account_type': account_type,
|
|
370
|
+
'account_num': account_num,
|
|
371
|
+
'response_account_type': response_account_type,
|
|
372
|
+
'response_account_num': response_account_num,
|
|
373
|
+
'user': None,
|
|
374
|
+
'tag': None,
|
|
375
|
+
'service_line': None
|
|
376
|
+
}, ensure_ascii=False), gname='info_')
|
|
377
|
+
|
|
378
|
+
return response
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def journallog_out(func):
|
|
382
|
+
|
|
383
|
+
@functools.wraps(func)
|
|
384
|
+
def inner(
|
|
385
|
+
self, method, url,
|
|
386
|
+
headers=None, params=None, data=None, json=None,
|
|
387
|
+
**kw
|
|
388
|
+
):
|
|
389
|
+
if headers is None:
|
|
390
|
+
headers = {'User-Agent': this.syscode}
|
|
391
|
+
else:
|
|
392
|
+
headers['User-Agent'] = this.syscode
|
|
393
|
+
|
|
394
|
+
f_back = inspect.currentframe().f_back.f_back
|
|
395
|
+
if f_back.f_back is not None:
|
|
396
|
+
f_back = f_back.f_back
|
|
397
|
+
method_name = getattr(f_back.f_code, co_qualname)
|
|
398
|
+
|
|
399
|
+
parse_url = urlparse(url)
|
|
400
|
+
address = parse_url.scheme + '://' + parse_url.netloc + parse_url.path
|
|
401
|
+
query_string = {k: v[0] for k, v in parse_qs(parse_url.query).items()}
|
|
402
|
+
|
|
403
|
+
if params is not None:
|
|
404
|
+
params.update(query_string)
|
|
405
|
+
request_data = params
|
|
406
|
+
elif query_string:
|
|
407
|
+
request_data = query_string
|
|
408
|
+
elif data:
|
|
409
|
+
request_data = data
|
|
410
|
+
if isinstance(request_data, (str, unicode)):
|
|
411
|
+
try:
|
|
412
|
+
request_data = jsonx.loads(request_data)
|
|
413
|
+
except ValueError:
|
|
414
|
+
pass
|
|
415
|
+
elif json:
|
|
416
|
+
request_data = json
|
|
417
|
+
else:
|
|
418
|
+
request_data = None
|
|
419
|
+
|
|
420
|
+
if Flask is not None and has_request_context():
|
|
421
|
+
transaction_id = g.transaction_id
|
|
422
|
+
else:
|
|
423
|
+
transaction_id = (
|
|
424
|
+
FindTransactionID(headers).result or
|
|
425
|
+
FindTransactionID(request_data).result or
|
|
426
|
+
uuid.uuid4().hex
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
for name in headers.copy():
|
|
430
|
+
namex = name.replace('-', '').replace('_', '').lower()
|
|
431
|
+
if namex == 'transactionid':
|
|
432
|
+
del headers[name]
|
|
433
|
+
headers['Transaction-ID'] = transaction_id
|
|
434
|
+
|
|
435
|
+
request_time = datetime.now()
|
|
436
|
+
response = func(
|
|
437
|
+
self, method, url,
|
|
438
|
+
headers=headers, params=params, data=data, json=json,
|
|
439
|
+
**kw
|
|
440
|
+
)
|
|
441
|
+
response_time = datetime.now()
|
|
442
|
+
response_time_str = response_time.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
|
|
443
|
+
|
|
444
|
+
try:
|
|
445
|
+
response_data = response.json()
|
|
446
|
+
except ValueError:
|
|
447
|
+
response_data = response_code = order_id = \
|
|
448
|
+
province_code = city_code = \
|
|
449
|
+
account_type = account_num = \
|
|
450
|
+
response_account_type = response_account_num = None
|
|
451
|
+
else:
|
|
452
|
+
if isinstance(response_data, dict):
|
|
453
|
+
head = response_data.get('head')
|
|
454
|
+
x = head if isinstance(head, dict) else response_data
|
|
455
|
+
response_code = x.get('code')
|
|
456
|
+
order_id = x.get('order_id')
|
|
457
|
+
province_code = x.get('province_code')
|
|
458
|
+
city_code = x.get('city_code')
|
|
459
|
+
account_type = x.get('account_type')
|
|
460
|
+
account_num = x.get('account_num')
|
|
461
|
+
response_account_type = x.get('response_account_type')
|
|
462
|
+
response_account_num = x.get('response_account_num')
|
|
463
|
+
else:
|
|
464
|
+
response_code = order_id = \
|
|
465
|
+
province_code = city_code = \
|
|
466
|
+
account_type = account_num = \
|
|
467
|
+
response_account_type = response_account_num = None
|
|
468
|
+
|
|
469
|
+
total_time = round((response_time - request_time).total_seconds(), 3)
|
|
470
|
+
|
|
471
|
+
glog.info(jsonx.dumps({
|
|
472
|
+
'app_name': this.appname + '_info',
|
|
473
|
+
'level': 'INFO',
|
|
474
|
+
'log_time': response_time_str,
|
|
475
|
+
'logger': __package__,
|
|
476
|
+
'thread': threading.current_thread().ident,
|
|
477
|
+
'transaction_id': transaction_id,
|
|
478
|
+
'dialog_type': 'out',
|
|
479
|
+
'address': address,
|
|
480
|
+
'fcode': this.syscode,
|
|
481
|
+
'tcode': this.syscode,
|
|
482
|
+
'method_code': None,
|
|
483
|
+
'method_name': method_name,
|
|
484
|
+
'http_method': response.request.method,
|
|
485
|
+
'request_time': request_time.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3],
|
|
486
|
+
'request_headers': dict(response.request.headers),
|
|
487
|
+
'request_payload': OmitLongString(request_data),
|
|
488
|
+
'response_time': response_time_str,
|
|
489
|
+
'response_headers': dict(response.headers),
|
|
490
|
+
'response_payload': OmitLongString(response_data),
|
|
491
|
+
'response_code': response_code,
|
|
492
|
+
'response_remark': None,
|
|
493
|
+
'http_status_code': response.status_code,
|
|
494
|
+
'order_id': order_id,
|
|
495
|
+
'province_code': province_code,
|
|
496
|
+
'city_code': city_code,
|
|
497
|
+
'total_time': total_time,
|
|
498
|
+
'error_code': response_code,
|
|
499
|
+
'request_ip': parse_url.hostname,
|
|
500
|
+
'host_ip': socket.gethostbyname(socket.gethostname()),
|
|
501
|
+
'host_name': socket.gethostname(),
|
|
502
|
+
'account_type': account_type,
|
|
503
|
+
'account_num': account_num,
|
|
504
|
+
'response_account_type': response_account_type,
|
|
505
|
+
'response_account_num': response_account_num,
|
|
506
|
+
'user': None,
|
|
507
|
+
'tag': None,
|
|
508
|
+
'service_line': None
|
|
509
|
+
}, ensure_ascii=False), gname='info_')
|
|
510
|
+
|
|
511
|
+
return response
|
|
512
|
+
|
|
513
|
+
return inner
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
class OmitLongString(dict):
|
|
517
|
+
|
|
518
|
+
def __init__(self, data):
|
|
519
|
+
for name, value in data.items():
|
|
520
|
+
dict.__setitem__(self, name, OmitLongString(value))
|
|
521
|
+
|
|
522
|
+
def __new__(cls, data):
|
|
523
|
+
if isinstance(data, dict):
|
|
524
|
+
return dict.__new__(cls)
|
|
525
|
+
|
|
526
|
+
if isinstance(data, (list, tuple)):
|
|
527
|
+
return data.__class__(cls(v) for v in data)
|
|
528
|
+
|
|
529
|
+
if sys.version_info.major < 3 and isinstance(data, str):
|
|
530
|
+
data = data.decode('utf8')
|
|
531
|
+
|
|
532
|
+
if isinstance(data, (unicode, str)) and len(data) > 1000:
|
|
533
|
+
data = '<Ellipsis>'
|
|
534
|
+
|
|
535
|
+
return data
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
class FindTransactionID(dict):
|
|
539
|
+
result = None
|
|
540
|
+
|
|
541
|
+
def __init__(self, data, root=None):
|
|
542
|
+
if root is None:
|
|
543
|
+
root = self
|
|
544
|
+
for k, v in data.items():
|
|
545
|
+
if k.replace('-', '').replace('_', '').lower() == 'transactionid':
|
|
546
|
+
root.result = data[k]
|
|
547
|
+
break
|
|
548
|
+
dict.__setitem__(self, k, FindTransactionID(v, root=root))
|
|
549
|
+
|
|
550
|
+
def __new__(cls, data, root=None):
|
|
551
|
+
if isinstance(data, dict):
|
|
552
|
+
return dict.__new__(cls)
|
|
553
|
+
if isinstance(data, (list, tuple)):
|
|
554
|
+
return data.__class__(cls(v) for v in data)
|
|
555
|
+
return cls
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: simple-channel-log
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: 轻量高效的日志库,支持多级别日志记录、日志轮转、流水日志追踪及埋点日志功能,深度集成 Flask 和 Requests 框架。
|
|
5
|
+
Author: Unnamed great master
|
|
6
|
+
Author-email: <gqylpy@outlook.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Source, https://github.com/2018-11-27/simple-channel-log
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
|
+
Classifier: Natural Language :: Chinese (Simplified)
|
|
13
|
+
Classifier: Natural Language :: English
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Classifier: Topic :: Artistic Software
|
|
17
|
+
Classifier: Topic :: Internet :: Log Analysis
|
|
18
|
+
Classifier: Topic :: Text Processing
|
|
19
|
+
Classifier: Programming Language :: Python :: 2.7
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.0
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.1
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.2
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.3
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.4
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.5
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
28
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
29
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
34
|
+
Requires-Python: >=2.7
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
License-File: LICENSE
|
|
37
|
+
Requires-Dist: gqylpy_log==0.0a1
|
|
38
|
+
Provides-Extra: flask
|
|
39
|
+
Requires-Dist: Flask>=0.10; extra == "flask"
|
|
40
|
+
Provides-Extra: requests
|
|
41
|
+
Requires-Dist: requests>=2.19; extra == "requests"
|
|
42
|
+
Dynamic: author
|
|
43
|
+
Dynamic: author-email
|
|
44
|
+
Dynamic: classifier
|
|
45
|
+
Dynamic: description
|
|
46
|
+
Dynamic: description-content-type
|
|
47
|
+
Dynamic: license
|
|
48
|
+
Dynamic: project-url
|
|
49
|
+
Dynamic: provides-extra
|
|
50
|
+
Dynamic: requires-dist
|
|
51
|
+
Dynamic: requires-python
|
|
52
|
+
Dynamic: summary
|
|
53
|
+
|
|
54
|
+
# simple-channel-log
|
|
55
|
+
|
|
56
|
+
[](https://github.com/2018-11-27/simple-channel-log/releases/latest)
|
|
57
|
+
[](https://github.com/2018-11-27/simple-channel-log)
|
|
58
|
+
[](https://opensource.org/licenses/MIT)
|
|
59
|
+
[](https://pepy.tech/project/simple-channel-log)
|
|
60
|
+
|
|
61
|
+
轻量高效的日志库,支持多级别日志记录、日志轮转、流水日志追踪及埋点日志功能,深度集成 Flask 和 Requests 框架。
|
|
62
|
+
|
|
63
|
+
## 主要特性
|
|
64
|
+
|
|
65
|
+
- 📅 支持按时间轮转日志(天/小时/分钟等)
|
|
66
|
+
- 📊 提供多级别日志记录(DEBUG/INFO/WARNING/ERROR/CRITICAL)
|
|
67
|
+
- 🌐 内置Flask中间件记录请求/响应流水日志(Journallog-in)
|
|
68
|
+
- 📡 集成Requests会话记录外部调用流水日志(Journallog-out)
|
|
69
|
+
- 🔍 智能处理长字符串截断(超过1000字符自动标记)
|
|
70
|
+
- 📁 自动创建分级日志目录结构
|
|
71
|
+
- 💻 支持终端输出与文件存储分离控制
|
|
72
|
+
|
|
73
|
+
## 安装
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install simple_channel_log
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 快速入门
|
|
80
|
+
|
|
81
|
+
### 基础配置
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
# coding:utf-8
|
|
85
|
+
import simple_channel_log as log
|
|
86
|
+
|
|
87
|
+
# 初始化日志配置
|
|
88
|
+
log.__init__("<your_appname>", "<your_syscode>", logdir="/app/logs")
|
|
89
|
+
|
|
90
|
+
# 初始化后可直接调用日志方法,日志将记录到参数 `logdir` 指定的目录中
|
|
91
|
+
log.debug("调试信息", extra_field="value")
|
|
92
|
+
log.info("业务日志", user_id=123)
|
|
93
|
+
log.warning("异常预警", error_code=500)
|
|
94
|
+
log.error("系统错误", stack_trace="...")
|
|
95
|
+
|
|
96
|
+
# 埋点日志
|
|
97
|
+
log.trace(
|
|
98
|
+
user_id=123,
|
|
99
|
+
action="purchase",
|
|
100
|
+
item_count=2
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Flask 流水日志
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# coding:utf-8
|
|
108
|
+
import simple_channel_log as log
|
|
109
|
+
|
|
110
|
+
# 初始化日志配置
|
|
111
|
+
log.__init__(..., enable_journallog_in=True)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
设置 `enable_journallog_in=True` 表示启用 Flask 流水日志,将自动记录每个接口的调用信息。
|
|
115
|
+
|
|
116
|
+
### Requests 外部调用追踪
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
# coding:utf-8
|
|
120
|
+
import simple_channel_log as log
|
|
121
|
+
|
|
122
|
+
# 初始化日志配置
|
|
123
|
+
log.__init__(..., enable_journallog_out=True)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
设置 `enable_journallog_out=True` 表示启用 Requests 外部调用追踪,将自动记录每个请求的调用信息。
|
|
127
|
+
|
|
128
|
+
## 详细配置
|
|
129
|
+
|
|
130
|
+
### 初始化参数
|
|
131
|
+
|
|
132
|
+
| 参数名 | 类型 | 默认值 | 说明 |
|
|
133
|
+
|-----------------------|------|----------|-------------------------------|
|
|
134
|
+
| appname | str | 必填 | 应用名称,建议与CI配置一致 |
|
|
135
|
+
| syscode | str | 必填 | 系统编码(大写) |
|
|
136
|
+
| logdir | str | 系统相关默认路径 | 日志存储根目录 |
|
|
137
|
+
| when | str | 'D' | 轮转周期:W(周)/D(天)/H(时)/M(分)/S(秒) |
|
|
138
|
+
| interval | int | 1 | 轮转频率 |
|
|
139
|
+
| backup_count | int | 7 | 历史日志保留数量(0=永久) |
|
|
140
|
+
| output_to_terminal | bool | False | 启用后日志将同时输出到控制台 |
|
|
141
|
+
| enable_journallog_in | bool | False | 启用Flask请求流水日志 |
|
|
142
|
+
| enable_journallog_out | bool | False | 启用Requests外部调用日志 |
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
simple_channel_log/__init__.py
|
|
5
|
+
simple_channel_log/i simple_channel_log.py
|
|
6
|
+
simple_channel_log.egg-info/PKG-INFO
|
|
7
|
+
simple_channel_log.egg-info/SOURCES.txt
|
|
8
|
+
simple_channel_log.egg-info/dependency_links.txt
|
|
9
|
+
simple_channel_log.egg-info/requires.txt
|
|
10
|
+
simple_channel_log.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
simple_channel_log
|