pycommonlog 0.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.
- pycommonlog-0.0.0/LICENSE +21 -0
- pycommonlog-0.0.0/PKG-INFO +243 -0
- pycommonlog-0.0.0/README.md +218 -0
- pycommonlog-0.0.0/pycommonlog/__init__.py +20 -0
- pycommonlog-0.0.0/pycommonlog/log_types.py +70 -0
- pycommonlog-0.0.0/pycommonlog/logger.py +151 -0
- pycommonlog-0.0.0/pycommonlog.egg-info/PKG-INFO +243 -0
- pycommonlog-0.0.0/pycommonlog.egg-info/SOURCES.txt +10 -0
- pycommonlog-0.0.0/pycommonlog.egg-info/dependency_links.txt +1 -0
- pycommonlog-0.0.0/pycommonlog.egg-info/top_level.txt +1 -0
- pycommonlog-0.0.0/setup.cfg +4 -0
- pycommonlog-0.0.0/setup.py +35 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Alvian Rahman Hanif
|
|
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,243 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pycommonlog
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Unified logging and alerting library for Python.
|
|
5
|
+
Home-page: https://github.com/alvianhanif/pycommonlog
|
|
6
|
+
Author: Alvian Rahman Hanif
|
|
7
|
+
Author-email: alvian.hanif@pasarpolis.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: author-email
|
|
17
|
+
Dynamic: classifier
|
|
18
|
+
Dynamic: description
|
|
19
|
+
Dynamic: description-content-type
|
|
20
|
+
Dynamic: home-page
|
|
21
|
+
Dynamic: license
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
Dynamic: requires-python
|
|
24
|
+
Dynamic: summary
|
|
25
|
+
|
|
26
|
+
# pycommonlog
|
|
27
|
+
|
|
28
|
+
[](https://github.com/alvianhanif/pycommonlog/actions/workflows/ci.yml)
|
|
29
|
+
[](https://badge.fury.io/py/pycommonlog)
|
|
30
|
+
[](https://opensource.org/licenses/MIT)
|
|
31
|
+
|
|
32
|
+
A unified logging and alerting library for Python, supporting Slack and Lark integrations via WebClient and Webhook. Features configurable providers, alert levels, and file attachment support.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
Install via pip:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install pycommonlog
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or copy the `pycommonlog/` directory to your project.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from pycommonlog import commonlog, Config, SendMethod, AlertLevel, Attachment, LarkToken
|
|
49
|
+
|
|
50
|
+
# Configure logger
|
|
51
|
+
config = Config(
|
|
52
|
+
provider="lark", # or "slack"
|
|
53
|
+
send_method=SendMethod.WEBCLIENT,
|
|
54
|
+
token="app_id++app_secret", # for Lark, use "app_id++app_secret" format
|
|
55
|
+
slack_token="xoxb-your-slack-token", # dedicated Slack token
|
|
56
|
+
lark_token=LarkToken(app_id="your-app-id", app_secret="your-app-secret"), # dedicated Lark token
|
|
57
|
+
channel="your_lark_channel_id",
|
|
58
|
+
redis_host="localhost", # required for Lark
|
|
59
|
+
redis_port="6379", # required for Lark
|
|
60
|
+
)
|
|
61
|
+
logger = commonlog(config)
|
|
62
|
+
|
|
63
|
+
# Send error with attachment
|
|
64
|
+
try:
|
|
65
|
+
logger.send(AlertLevel.ERROR, "System error occurred", Attachment(url="https://example.com/log.txt"))
|
|
66
|
+
except Exception as e:
|
|
67
|
+
print(f"Failed to send alert: {e}")
|
|
68
|
+
|
|
69
|
+
# Send info (logs only)
|
|
70
|
+
logger.send(AlertLevel.INFO, "Info message")
|
|
71
|
+
|
|
72
|
+
# Send to a specific channel
|
|
73
|
+
try:
|
|
74
|
+
logger.send_to_channel(AlertLevel.ERROR, "Send to another channel", channel="another-channel-id")
|
|
75
|
+
except Exception as e:
|
|
76
|
+
print(f"Failed to send alert: {e}")
|
|
77
|
+
|
|
78
|
+
# Send to a different provider dynamically
|
|
79
|
+
try:
|
|
80
|
+
logger.custom_send("slack", AlertLevel.ERROR, "Message via Slack", channel="slack-channel")
|
|
81
|
+
except Exception as e:
|
|
82
|
+
print(f"Failed to send alert: {e}")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Send Methods
|
|
86
|
+
|
|
87
|
+
commonlog supports two send methods: WebClient (API-based) and Webhook (simple HTTP POST).
|
|
88
|
+
|
|
89
|
+
### WebClient Usage
|
|
90
|
+
|
|
91
|
+
WebClient uses the full API with authentication tokens:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
config = Config(
|
|
95
|
+
provider="lark",
|
|
96
|
+
send_method=SendMethod.WEBCLIENT,
|
|
97
|
+
token="app_id++app_secret", # for Lark
|
|
98
|
+
slack_token="xoxb-your-slack-token", # for Slack
|
|
99
|
+
lark_token=LarkToken(app_id="your-app-id", app_secret="your-app-secret"),
|
|
100
|
+
channel="your_channel",
|
|
101
|
+
redis_host="localhost", # required for Lark
|
|
102
|
+
redis_port="6379",
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Webhook Usage
|
|
107
|
+
|
|
108
|
+
Webhook is simpler and requires only a webhook URL:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
config = Config(
|
|
112
|
+
provider="slack",
|
|
113
|
+
send_method=SendMethod.WEBHOOK,
|
|
114
|
+
token="https://hooks.slack.com/services/YOUR/WEBHOOK/URL",
|
|
115
|
+
channel="optional-channel-override", # optional
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Lark Token Caching
|
|
120
|
+
|
|
121
|
+
When using Lark, the tenant_access_token is cached in Redis. The expiry is set dynamically from the API response minus 10 minutes. You must set `redis_host` and `redis_port` in your config.
|
|
122
|
+
|
|
123
|
+
## Channel Mapping
|
|
124
|
+
|
|
125
|
+
You can configure different channels for different alert levels using a channel resolver:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from commonlog import commonlog, Config, SendMethod, AlertLevel, DefaultChannelResolver
|
|
129
|
+
|
|
130
|
+
# Create a channel resolver
|
|
131
|
+
resolver = DefaultChannelResolver(
|
|
132
|
+
channel_map={
|
|
133
|
+
AlertLevel.INFO: "#general",
|
|
134
|
+
AlertLevel.WARN: "#warnings",
|
|
135
|
+
AlertLevel.ERROR: "#alerts",
|
|
136
|
+
},
|
|
137
|
+
default_channel="#general"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Create config with channel resolver
|
|
141
|
+
config = Config(
|
|
142
|
+
provider="slack",
|
|
143
|
+
send_method=SendMethod.WEBCLIENT,
|
|
144
|
+
token="xoxb-your-slack-bot-token",
|
|
145
|
+
channel_resolver=resolver,
|
|
146
|
+
service_name="user-service",
|
|
147
|
+
environment="production"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
logger = commonlog(config)
|
|
151
|
+
|
|
152
|
+
# These will go to different channels based on level
|
|
153
|
+
logger.send(AlertLevel.INFO, "Info message") # goes to #general
|
|
154
|
+
logger.send(AlertLevel.WARN, "Warning message") # goes to #warnings
|
|
155
|
+
logger.send(AlertLevel.ERROR, "Error message") # goes to #alerts
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Custom Channel Resolver
|
|
159
|
+
|
|
160
|
+
You can implement custom channel resolution logic:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
class CustomResolver(ChannelResolver):
|
|
164
|
+
def resolve_channel(self, level):
|
|
165
|
+
if level == AlertLevel.ERROR:
|
|
166
|
+
return "#critical-alerts"
|
|
167
|
+
elif level == AlertLevel.WARN:
|
|
168
|
+
return "#monitoring"
|
|
169
|
+
else:
|
|
170
|
+
return "#general"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Configuration Options
|
|
174
|
+
|
|
175
|
+
### Common Settings
|
|
176
|
+
|
|
177
|
+
- **provider**: `"slack"` or `"lark"`
|
|
178
|
+
- **send_method**: `"webclient"` (token-based authentication)
|
|
179
|
+
- **channel**: Target channel or chat ID (used if no resolver)
|
|
180
|
+
- **channel_resolver**: Optional resolver for dynamic channel mapping
|
|
181
|
+
- **service_name**: Name of the service sending alerts
|
|
182
|
+
- **environment**: Environment (dev, staging, production)
|
|
183
|
+
- **debug**: `True` to enable detailed debug logging of all internal processes
|
|
184
|
+
|
|
185
|
+
### Provider-Specific
|
|
186
|
+
|
|
187
|
+
- **token**: API token for WebClient authentication (required)
|
|
188
|
+
|
|
189
|
+
## Alert Levels
|
|
190
|
+
|
|
191
|
+
- **INFO**: Logs locally only
|
|
192
|
+
- **WARN**: Logs + sends alert
|
|
193
|
+
- **ERROR**: Always sends alert
|
|
194
|
+
|
|
195
|
+
## File Attachments
|
|
196
|
+
|
|
197
|
+
Provide a public URL. The library appends it to the message for simplicity.
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
attachment = Attachment(url="https://example.com/log.txt")
|
|
201
|
+
logger.send(AlertLevel.ERROR, "Error with log", attachment)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Trace Log Section
|
|
205
|
+
|
|
206
|
+
When `include_trace` is set to `True`, you can pass trace information as the fourth parameter to `send()`:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
trace = """Traceback (most recent call last):
|
|
210
|
+
File "app.py", line 10, in main
|
|
211
|
+
raise ValueError("Something went wrong")
|
|
212
|
+
ValueError: Something went wrong"""
|
|
213
|
+
|
|
214
|
+
logger.send(AlertLevel.ERROR, "System error occurred", None, trace)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
This will format the trace as a code block in the alert message.
|
|
218
|
+
|
|
219
|
+
## Testing
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
cd python
|
|
223
|
+
PYTHONPATH=.. python -m unittest test_commonlog.py
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## API Reference
|
|
227
|
+
|
|
228
|
+
### Classes
|
|
229
|
+
|
|
230
|
+
- `Config`: Configuration class
|
|
231
|
+
- `Attachment`: File attachment class
|
|
232
|
+
- `Provider`: Abstract base class for alert providers
|
|
233
|
+
- `commonlog`: Main logger class
|
|
234
|
+
|
|
235
|
+
### Constants
|
|
236
|
+
|
|
237
|
+
- `SendMethod.WEBCLIENT`: Send method (token-based authentication)
|
|
238
|
+
- `AlertLevel.INFO`, `AlertLevel.WARN`, `AlertLevel.ERROR`: Alert levels
|
|
239
|
+
|
|
240
|
+
### Methods
|
|
241
|
+
|
|
242
|
+
- `commonlog(config)`: Create a new logger
|
|
243
|
+
- `commonlog.send(level, message, attachment=None, trace="")`: Send alert with optional trace
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# pycommonlog
|
|
2
|
+
|
|
3
|
+
[](https://github.com/alvianhanif/pycommonlog/actions/workflows/ci.yml)
|
|
4
|
+
[](https://badge.fury.io/py/pycommonlog)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
A unified logging and alerting library for Python, supporting Slack and Lark integrations via WebClient and Webhook. Features configurable providers, alert levels, and file attachment support.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Install via pip:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install pycommonlog
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or copy the `pycommonlog/` directory to your project.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from pycommonlog import commonlog, Config, SendMethod, AlertLevel, Attachment, LarkToken
|
|
24
|
+
|
|
25
|
+
# Configure logger
|
|
26
|
+
config = Config(
|
|
27
|
+
provider="lark", # or "slack"
|
|
28
|
+
send_method=SendMethod.WEBCLIENT,
|
|
29
|
+
token="app_id++app_secret", # for Lark, use "app_id++app_secret" format
|
|
30
|
+
slack_token="xoxb-your-slack-token", # dedicated Slack token
|
|
31
|
+
lark_token=LarkToken(app_id="your-app-id", app_secret="your-app-secret"), # dedicated Lark token
|
|
32
|
+
channel="your_lark_channel_id",
|
|
33
|
+
redis_host="localhost", # required for Lark
|
|
34
|
+
redis_port="6379", # required for Lark
|
|
35
|
+
)
|
|
36
|
+
logger = commonlog(config)
|
|
37
|
+
|
|
38
|
+
# Send error with attachment
|
|
39
|
+
try:
|
|
40
|
+
logger.send(AlertLevel.ERROR, "System error occurred", Attachment(url="https://example.com/log.txt"))
|
|
41
|
+
except Exception as e:
|
|
42
|
+
print(f"Failed to send alert: {e}")
|
|
43
|
+
|
|
44
|
+
# Send info (logs only)
|
|
45
|
+
logger.send(AlertLevel.INFO, "Info message")
|
|
46
|
+
|
|
47
|
+
# Send to a specific channel
|
|
48
|
+
try:
|
|
49
|
+
logger.send_to_channel(AlertLevel.ERROR, "Send to another channel", channel="another-channel-id")
|
|
50
|
+
except Exception as e:
|
|
51
|
+
print(f"Failed to send alert: {e}")
|
|
52
|
+
|
|
53
|
+
# Send to a different provider dynamically
|
|
54
|
+
try:
|
|
55
|
+
logger.custom_send("slack", AlertLevel.ERROR, "Message via Slack", channel="slack-channel")
|
|
56
|
+
except Exception as e:
|
|
57
|
+
print(f"Failed to send alert: {e}")
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Send Methods
|
|
61
|
+
|
|
62
|
+
commonlog supports two send methods: WebClient (API-based) and Webhook (simple HTTP POST).
|
|
63
|
+
|
|
64
|
+
### WebClient Usage
|
|
65
|
+
|
|
66
|
+
WebClient uses the full API with authentication tokens:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
config = Config(
|
|
70
|
+
provider="lark",
|
|
71
|
+
send_method=SendMethod.WEBCLIENT,
|
|
72
|
+
token="app_id++app_secret", # for Lark
|
|
73
|
+
slack_token="xoxb-your-slack-token", # for Slack
|
|
74
|
+
lark_token=LarkToken(app_id="your-app-id", app_secret="your-app-secret"),
|
|
75
|
+
channel="your_channel",
|
|
76
|
+
redis_host="localhost", # required for Lark
|
|
77
|
+
redis_port="6379",
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Webhook Usage
|
|
82
|
+
|
|
83
|
+
Webhook is simpler and requires only a webhook URL:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
config = Config(
|
|
87
|
+
provider="slack",
|
|
88
|
+
send_method=SendMethod.WEBHOOK,
|
|
89
|
+
token="https://hooks.slack.com/services/YOUR/WEBHOOK/URL",
|
|
90
|
+
channel="optional-channel-override", # optional
|
|
91
|
+
)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Lark Token Caching
|
|
95
|
+
|
|
96
|
+
When using Lark, the tenant_access_token is cached in Redis. The expiry is set dynamically from the API response minus 10 minutes. You must set `redis_host` and `redis_port` in your config.
|
|
97
|
+
|
|
98
|
+
## Channel Mapping
|
|
99
|
+
|
|
100
|
+
You can configure different channels for different alert levels using a channel resolver:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from commonlog import commonlog, Config, SendMethod, AlertLevel, DefaultChannelResolver
|
|
104
|
+
|
|
105
|
+
# Create a channel resolver
|
|
106
|
+
resolver = DefaultChannelResolver(
|
|
107
|
+
channel_map={
|
|
108
|
+
AlertLevel.INFO: "#general",
|
|
109
|
+
AlertLevel.WARN: "#warnings",
|
|
110
|
+
AlertLevel.ERROR: "#alerts",
|
|
111
|
+
},
|
|
112
|
+
default_channel="#general"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Create config with channel resolver
|
|
116
|
+
config = Config(
|
|
117
|
+
provider="slack",
|
|
118
|
+
send_method=SendMethod.WEBCLIENT,
|
|
119
|
+
token="xoxb-your-slack-bot-token",
|
|
120
|
+
channel_resolver=resolver,
|
|
121
|
+
service_name="user-service",
|
|
122
|
+
environment="production"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
logger = commonlog(config)
|
|
126
|
+
|
|
127
|
+
# These will go to different channels based on level
|
|
128
|
+
logger.send(AlertLevel.INFO, "Info message") # goes to #general
|
|
129
|
+
logger.send(AlertLevel.WARN, "Warning message") # goes to #warnings
|
|
130
|
+
logger.send(AlertLevel.ERROR, "Error message") # goes to #alerts
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Custom Channel Resolver
|
|
134
|
+
|
|
135
|
+
You can implement custom channel resolution logic:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
class CustomResolver(ChannelResolver):
|
|
139
|
+
def resolve_channel(self, level):
|
|
140
|
+
if level == AlertLevel.ERROR:
|
|
141
|
+
return "#critical-alerts"
|
|
142
|
+
elif level == AlertLevel.WARN:
|
|
143
|
+
return "#monitoring"
|
|
144
|
+
else:
|
|
145
|
+
return "#general"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Configuration Options
|
|
149
|
+
|
|
150
|
+
### Common Settings
|
|
151
|
+
|
|
152
|
+
- **provider**: `"slack"` or `"lark"`
|
|
153
|
+
- **send_method**: `"webclient"` (token-based authentication)
|
|
154
|
+
- **channel**: Target channel or chat ID (used if no resolver)
|
|
155
|
+
- **channel_resolver**: Optional resolver for dynamic channel mapping
|
|
156
|
+
- **service_name**: Name of the service sending alerts
|
|
157
|
+
- **environment**: Environment (dev, staging, production)
|
|
158
|
+
- **debug**: `True` to enable detailed debug logging of all internal processes
|
|
159
|
+
|
|
160
|
+
### Provider-Specific
|
|
161
|
+
|
|
162
|
+
- **token**: API token for WebClient authentication (required)
|
|
163
|
+
|
|
164
|
+
## Alert Levels
|
|
165
|
+
|
|
166
|
+
- **INFO**: Logs locally only
|
|
167
|
+
- **WARN**: Logs + sends alert
|
|
168
|
+
- **ERROR**: Always sends alert
|
|
169
|
+
|
|
170
|
+
## File Attachments
|
|
171
|
+
|
|
172
|
+
Provide a public URL. The library appends it to the message for simplicity.
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
attachment = Attachment(url="https://example.com/log.txt")
|
|
176
|
+
logger.send(AlertLevel.ERROR, "Error with log", attachment)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Trace Log Section
|
|
180
|
+
|
|
181
|
+
When `include_trace` is set to `True`, you can pass trace information as the fourth parameter to `send()`:
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
trace = """Traceback (most recent call last):
|
|
185
|
+
File "app.py", line 10, in main
|
|
186
|
+
raise ValueError("Something went wrong")
|
|
187
|
+
ValueError: Something went wrong"""
|
|
188
|
+
|
|
189
|
+
logger.send(AlertLevel.ERROR, "System error occurred", None, trace)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
This will format the trace as a code block in the alert message.
|
|
193
|
+
|
|
194
|
+
## Testing
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
cd python
|
|
198
|
+
PYTHONPATH=.. python -m unittest test_commonlog.py
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## API Reference
|
|
202
|
+
|
|
203
|
+
### Classes
|
|
204
|
+
|
|
205
|
+
- `Config`: Configuration class
|
|
206
|
+
- `Attachment`: File attachment class
|
|
207
|
+
- `Provider`: Abstract base class for alert providers
|
|
208
|
+
- `commonlog`: Main logger class
|
|
209
|
+
|
|
210
|
+
### Constants
|
|
211
|
+
|
|
212
|
+
- `SendMethod.WEBCLIENT`: Send method (token-based authentication)
|
|
213
|
+
- `AlertLevel.INFO`, `AlertLevel.WARN`, `AlertLevel.ERROR`: Alert levels
|
|
214
|
+
|
|
215
|
+
### Methods
|
|
216
|
+
|
|
217
|
+
- `commonlog(config)`: Create a new logger
|
|
218
|
+
- `commonlog.send(level, message, attachment=None, trace="")`: Send alert with optional trace
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
commonlog: Unified logging and alerting for Slack/Lark (Python)
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .log_types import SendMethod, AlertLevel, Attachment, Config, Provider, ChannelResolver, DefaultChannelResolver
|
|
6
|
+
from .providers import SlackProvider, LarkProvider
|
|
7
|
+
from .logger import commonlog
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"SendMethod",
|
|
11
|
+
"AlertLevel",
|
|
12
|
+
"Attachment",
|
|
13
|
+
"Config",
|
|
14
|
+
"Provider",
|
|
15
|
+
"ChannelResolver",
|
|
16
|
+
"DefaultChannelResolver",
|
|
17
|
+
"SlackProvider",
|
|
18
|
+
"LarkProvider",
|
|
19
|
+
"commonlog"
|
|
20
|
+
]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
commonlog: Unified logging and alerting for Slack/Lark (Python)
|
|
3
|
+
"""
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
|
|
6
|
+
class SendMethod:
|
|
7
|
+
WEBCLIENT = "webclient"
|
|
8
|
+
WEBHOOK = "webhook"
|
|
9
|
+
|
|
10
|
+
class AlertLevel:
|
|
11
|
+
INFO = 0
|
|
12
|
+
WARN = 1
|
|
13
|
+
ERROR = 2
|
|
14
|
+
|
|
15
|
+
class Attachment:
|
|
16
|
+
def __init__(self, url=None, file_name=None, content=None):
|
|
17
|
+
self.url = url
|
|
18
|
+
self.file_name = file_name
|
|
19
|
+
self.content = content
|
|
20
|
+
|
|
21
|
+
class ChannelResolver(ABC):
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def resolve_channel(self, level):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
class DefaultChannelResolver(ChannelResolver):
|
|
27
|
+
def __init__(self, channel_map=None, default_channel=None):
|
|
28
|
+
self.channel_map = channel_map or {}
|
|
29
|
+
self.default_channel = default_channel
|
|
30
|
+
|
|
31
|
+
def resolve_channel(self, level):
|
|
32
|
+
return self.channel_map.get(level, self.default_channel)
|
|
33
|
+
|
|
34
|
+
class LarkToken:
|
|
35
|
+
def __init__(self, app_id=None, app_secret=None):
|
|
36
|
+
self.app_id = app_id
|
|
37
|
+
self.app_secret = app_secret
|
|
38
|
+
|
|
39
|
+
class Config:
|
|
40
|
+
def __init__(self, provider, send_method, token=None, slack_token=None, lark_token=None, channel=None, channel_resolver=None, service_name=None, environment=None, redis_host=None, redis_port=None, debug=False):
|
|
41
|
+
self.provider = provider
|
|
42
|
+
self.send_method = send_method
|
|
43
|
+
self.token = token
|
|
44
|
+
self.slack_token = slack_token
|
|
45
|
+
self.lark_token = lark_token
|
|
46
|
+
self.channel = channel
|
|
47
|
+
self.channel_resolver = channel_resolver
|
|
48
|
+
self.service_name = service_name
|
|
49
|
+
self.environment = environment
|
|
50
|
+
self.redis_host = redis_host
|
|
51
|
+
self.redis_port = redis_port
|
|
52
|
+
self.debug = debug
|
|
53
|
+
|
|
54
|
+
class Provider(ABC):
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def send_to_channel(self, level, message, attachment, config, channel):
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
# Debug logging
|
|
60
|
+
import logging
|
|
61
|
+
|
|
62
|
+
debug_logger = logging.getLogger('commonlog.debug')
|
|
63
|
+
debug_logger.setLevel(logging.DEBUG)
|
|
64
|
+
handler = logging.StreamHandler()
|
|
65
|
+
handler.setFormatter(logging.Formatter('[COMMONLOG DEBUG] %(filename)s:%(lineno)d - %(message)s'))
|
|
66
|
+
debug_logger.addHandler(handler)
|
|
67
|
+
|
|
68
|
+
def debug_log(config, message, *args):
|
|
69
|
+
if hasattr(config, 'debug') and config.debug:
|
|
70
|
+
debug_logger.debug(message, *args)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main logger for commonlog
|
|
3
|
+
"""
|
|
4
|
+
import logging
|
|
5
|
+
import sys
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
# Add current directory to path for direct imports
|
|
9
|
+
_current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
10
|
+
if _current_dir not in sys.path:
|
|
11
|
+
sys.path.insert(0, _current_dir)
|
|
12
|
+
|
|
13
|
+
from providers import SlackProvider, LarkProvider
|
|
14
|
+
from log_types import AlertLevel, Attachment, debug_log
|
|
15
|
+
|
|
16
|
+
# ====================
|
|
17
|
+
# Configuration and Logger
|
|
18
|
+
# ====================
|
|
19
|
+
|
|
20
|
+
class commonlog:
|
|
21
|
+
def send_to_channel(self, level, message, attachment=None, trace="", channel=None):
|
|
22
|
+
debug_log(self.config, f"send_to_channel called with level: {level}, message length: {len(message)}, channel: {channel}, has attachment: {attachment is not None}, has trace: {bool(trace)}")
|
|
23
|
+
|
|
24
|
+
if level == AlertLevel.INFO:
|
|
25
|
+
logging.info(message)
|
|
26
|
+
debug_log(self.config, "INFO level message logged locally, skipping provider send")
|
|
27
|
+
return
|
|
28
|
+
try:
|
|
29
|
+
# Use provided channel or fallback to resolved channel
|
|
30
|
+
target_channel = channel if channel else self._resolve_channel(level)
|
|
31
|
+
if channel is None:
|
|
32
|
+
debug_log(self.config, f"Resolved channel using resolver: {target_channel}")
|
|
33
|
+
else:
|
|
34
|
+
debug_log(self.config, f"Using provided channel: {target_channel}")
|
|
35
|
+
|
|
36
|
+
original_channel = self.config.channel
|
|
37
|
+
self.config.channel = target_channel
|
|
38
|
+
if trace:
|
|
39
|
+
debug_log(self.config, f"Processing trace attachment, trace length: {len(trace)}")
|
|
40
|
+
if attachment is None:
|
|
41
|
+
attachment = Attachment(content=trace, file_name="trace.log")
|
|
42
|
+
debug_log(self.config, "Created new trace attachment")
|
|
43
|
+
else:
|
|
44
|
+
if attachment.content:
|
|
45
|
+
attachment.content += "\n\n--- Trace Log ---\n" + trace
|
|
46
|
+
debug_log(self.config, "Appended trace to existing attachment content")
|
|
47
|
+
else:
|
|
48
|
+
attachment.content = trace
|
|
49
|
+
attachment.file_name = "trace.log"
|
|
50
|
+
debug_log(self.config, "Set trace as attachment content")
|
|
51
|
+
|
|
52
|
+
debug_log(self.config, f"Calling provider.send_to_channel with resolved channel: {target_channel}")
|
|
53
|
+
self.provider.send_to_channel(level, message, attachment, self.config, target_channel)
|
|
54
|
+
self.config.channel = original_channel
|
|
55
|
+
debug_log(self.config, "Provider send_to_channel completed successfully")
|
|
56
|
+
except Exception as e:
|
|
57
|
+
debug_log(self.config, f"Provider send_to_channel failed: {e}")
|
|
58
|
+
logging.error(f"Failed to send alert: {e}")
|
|
59
|
+
raise
|
|
60
|
+
|
|
61
|
+
def custom_send(self, provider, level, message, attachment=None, trace="", channel=None):
|
|
62
|
+
debug_log(self.config, f"custom_send called with custom provider: {provider}, level: {level}, message length: {len(message)}")
|
|
63
|
+
|
|
64
|
+
if provider == "slack":
|
|
65
|
+
custom_provider = SlackProvider()
|
|
66
|
+
elif provider == "lark":
|
|
67
|
+
custom_provider = LarkProvider()
|
|
68
|
+
else:
|
|
69
|
+
logging.warning(f"Unknown provider: {provider}, defaulting to Slack")
|
|
70
|
+
custom_provider = SlackProvider()
|
|
71
|
+
debug_log(self.config, f"Unknown provider '{provider}', defaulted to slack")
|
|
72
|
+
|
|
73
|
+
debug_log(self.config, f"Created custom provider: {provider}")
|
|
74
|
+
|
|
75
|
+
if level == AlertLevel.INFO:
|
|
76
|
+
logging.info(message)
|
|
77
|
+
debug_log(self.config, "INFO level message logged locally for custom provider, skipping send")
|
|
78
|
+
return
|
|
79
|
+
try:
|
|
80
|
+
# Use provided channel or fallback to resolved channel
|
|
81
|
+
target_channel = channel if channel else self._resolve_channel(level)
|
|
82
|
+
debug_log(self.config, f"Resolved channel for custom send: {target_channel}")
|
|
83
|
+
|
|
84
|
+
original_channel = self.config.channel
|
|
85
|
+
self.config.channel = target_channel
|
|
86
|
+
if trace:
|
|
87
|
+
debug_log(self.config, f"Processing trace for custom send, trace length: {len(trace)}")
|
|
88
|
+
if attachment is None:
|
|
89
|
+
attachment = Attachment(content=trace, file_name="trace.log")
|
|
90
|
+
else:
|
|
91
|
+
if attachment.content:
|
|
92
|
+
attachment.content += "\n\n--- Trace Log ---\n" + trace
|
|
93
|
+
else:
|
|
94
|
+
attachment.content = trace
|
|
95
|
+
attachment.file_name = "trace.log"
|
|
96
|
+
debug_log(self.config, f"Calling custom provider.send with provider: {provider}, channel: {target_channel}")
|
|
97
|
+
custom_provider.send(level, message, attachment, self.config)
|
|
98
|
+
self.config.channel = original_channel
|
|
99
|
+
debug_log(self.config, "Custom provider send completed successfully")
|
|
100
|
+
except Exception as e:
|
|
101
|
+
debug_log(self.config, f"Custom provider send failed: {e}")
|
|
102
|
+
logging.error(f"Failed to send alert: {e}")
|
|
103
|
+
raise
|
|
104
|
+
|
|
105
|
+
def __init__(self, config):
|
|
106
|
+
self.config = config
|
|
107
|
+
if config.provider == "slack":
|
|
108
|
+
self.provider = SlackProvider()
|
|
109
|
+
elif config.provider == "lark":
|
|
110
|
+
self.provider = LarkProvider()
|
|
111
|
+
else:
|
|
112
|
+
logging.warning(f"Unknown provider: {config.provider}, defaulting to Slack")
|
|
113
|
+
self.provider = SlackProvider()
|
|
114
|
+
|
|
115
|
+
debug_log(config, f"Created logger with provider: {config.provider}, send method: {config.send_method}, debug: {config.debug}")
|
|
116
|
+
|
|
117
|
+
def _resolve_channel(self, level):
|
|
118
|
+
if self.config.channel_resolver:
|
|
119
|
+
return self.config.channel_resolver.resolve_channel(level)
|
|
120
|
+
return self.config.channel
|
|
121
|
+
|
|
122
|
+
def send(self, level, message, attachment=None, trace=""):
|
|
123
|
+
if level == AlertLevel.INFO:
|
|
124
|
+
logging.info(message)
|
|
125
|
+
return
|
|
126
|
+
try:
|
|
127
|
+
# Resolve the channel for this alert level
|
|
128
|
+
resolved_channel = self._resolve_channel(level)
|
|
129
|
+
|
|
130
|
+
# Temporarily modify config with resolved channel
|
|
131
|
+
original_channel = self.config.channel
|
|
132
|
+
self.config.channel = resolved_channel
|
|
133
|
+
|
|
134
|
+
# If trace is provided, create an attachment
|
|
135
|
+
if trace:
|
|
136
|
+
if attachment is None:
|
|
137
|
+
attachment = Attachment(content=trace, file_name="trace.log")
|
|
138
|
+
else:
|
|
139
|
+
# If there's already an attachment, combine the trace content
|
|
140
|
+
if attachment.content:
|
|
141
|
+
attachment.content += "\n\n--- Trace Log ---\n" + trace
|
|
142
|
+
else:
|
|
143
|
+
attachment.content = trace
|
|
144
|
+
attachment.file_name = "trace.log"
|
|
145
|
+
self.provider.send(level, message, attachment, self.config)
|
|
146
|
+
|
|
147
|
+
# Restore original channel
|
|
148
|
+
self.config.channel = original_channel
|
|
149
|
+
except Exception as e:
|
|
150
|
+
logging.error(f"Failed to send alert: {e}")
|
|
151
|
+
raise
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pycommonlog
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Unified logging and alerting library for Python.
|
|
5
|
+
Home-page: https://github.com/alvianhanif/pycommonlog
|
|
6
|
+
Author: Alvian Rahman Hanif
|
|
7
|
+
Author-email: alvian.hanif@pasarpolis.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: author-email
|
|
17
|
+
Dynamic: classifier
|
|
18
|
+
Dynamic: description
|
|
19
|
+
Dynamic: description-content-type
|
|
20
|
+
Dynamic: home-page
|
|
21
|
+
Dynamic: license
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
Dynamic: requires-python
|
|
24
|
+
Dynamic: summary
|
|
25
|
+
|
|
26
|
+
# pycommonlog
|
|
27
|
+
|
|
28
|
+
[](https://github.com/alvianhanif/pycommonlog/actions/workflows/ci.yml)
|
|
29
|
+
[](https://badge.fury.io/py/pycommonlog)
|
|
30
|
+
[](https://opensource.org/licenses/MIT)
|
|
31
|
+
|
|
32
|
+
A unified logging and alerting library for Python, supporting Slack and Lark integrations via WebClient and Webhook. Features configurable providers, alert levels, and file attachment support.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
Install via pip:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install pycommonlog
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or copy the `pycommonlog/` directory to your project.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from pycommonlog import commonlog, Config, SendMethod, AlertLevel, Attachment, LarkToken
|
|
49
|
+
|
|
50
|
+
# Configure logger
|
|
51
|
+
config = Config(
|
|
52
|
+
provider="lark", # or "slack"
|
|
53
|
+
send_method=SendMethod.WEBCLIENT,
|
|
54
|
+
token="app_id++app_secret", # for Lark, use "app_id++app_secret" format
|
|
55
|
+
slack_token="xoxb-your-slack-token", # dedicated Slack token
|
|
56
|
+
lark_token=LarkToken(app_id="your-app-id", app_secret="your-app-secret"), # dedicated Lark token
|
|
57
|
+
channel="your_lark_channel_id",
|
|
58
|
+
redis_host="localhost", # required for Lark
|
|
59
|
+
redis_port="6379", # required for Lark
|
|
60
|
+
)
|
|
61
|
+
logger = commonlog(config)
|
|
62
|
+
|
|
63
|
+
# Send error with attachment
|
|
64
|
+
try:
|
|
65
|
+
logger.send(AlertLevel.ERROR, "System error occurred", Attachment(url="https://example.com/log.txt"))
|
|
66
|
+
except Exception as e:
|
|
67
|
+
print(f"Failed to send alert: {e}")
|
|
68
|
+
|
|
69
|
+
# Send info (logs only)
|
|
70
|
+
logger.send(AlertLevel.INFO, "Info message")
|
|
71
|
+
|
|
72
|
+
# Send to a specific channel
|
|
73
|
+
try:
|
|
74
|
+
logger.send_to_channel(AlertLevel.ERROR, "Send to another channel", channel="another-channel-id")
|
|
75
|
+
except Exception as e:
|
|
76
|
+
print(f"Failed to send alert: {e}")
|
|
77
|
+
|
|
78
|
+
# Send to a different provider dynamically
|
|
79
|
+
try:
|
|
80
|
+
logger.custom_send("slack", AlertLevel.ERROR, "Message via Slack", channel="slack-channel")
|
|
81
|
+
except Exception as e:
|
|
82
|
+
print(f"Failed to send alert: {e}")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Send Methods
|
|
86
|
+
|
|
87
|
+
commonlog supports two send methods: WebClient (API-based) and Webhook (simple HTTP POST).
|
|
88
|
+
|
|
89
|
+
### WebClient Usage
|
|
90
|
+
|
|
91
|
+
WebClient uses the full API with authentication tokens:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
config = Config(
|
|
95
|
+
provider="lark",
|
|
96
|
+
send_method=SendMethod.WEBCLIENT,
|
|
97
|
+
token="app_id++app_secret", # for Lark
|
|
98
|
+
slack_token="xoxb-your-slack-token", # for Slack
|
|
99
|
+
lark_token=LarkToken(app_id="your-app-id", app_secret="your-app-secret"),
|
|
100
|
+
channel="your_channel",
|
|
101
|
+
redis_host="localhost", # required for Lark
|
|
102
|
+
redis_port="6379",
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Webhook Usage
|
|
107
|
+
|
|
108
|
+
Webhook is simpler and requires only a webhook URL:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
config = Config(
|
|
112
|
+
provider="slack",
|
|
113
|
+
send_method=SendMethod.WEBHOOK,
|
|
114
|
+
token="https://hooks.slack.com/services/YOUR/WEBHOOK/URL",
|
|
115
|
+
channel="optional-channel-override", # optional
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Lark Token Caching
|
|
120
|
+
|
|
121
|
+
When using Lark, the tenant_access_token is cached in Redis. The expiry is set dynamically from the API response minus 10 minutes. You must set `redis_host` and `redis_port` in your config.
|
|
122
|
+
|
|
123
|
+
## Channel Mapping
|
|
124
|
+
|
|
125
|
+
You can configure different channels for different alert levels using a channel resolver:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from commonlog import commonlog, Config, SendMethod, AlertLevel, DefaultChannelResolver
|
|
129
|
+
|
|
130
|
+
# Create a channel resolver
|
|
131
|
+
resolver = DefaultChannelResolver(
|
|
132
|
+
channel_map={
|
|
133
|
+
AlertLevel.INFO: "#general",
|
|
134
|
+
AlertLevel.WARN: "#warnings",
|
|
135
|
+
AlertLevel.ERROR: "#alerts",
|
|
136
|
+
},
|
|
137
|
+
default_channel="#general"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Create config with channel resolver
|
|
141
|
+
config = Config(
|
|
142
|
+
provider="slack",
|
|
143
|
+
send_method=SendMethod.WEBCLIENT,
|
|
144
|
+
token="xoxb-your-slack-bot-token",
|
|
145
|
+
channel_resolver=resolver,
|
|
146
|
+
service_name="user-service",
|
|
147
|
+
environment="production"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
logger = commonlog(config)
|
|
151
|
+
|
|
152
|
+
# These will go to different channels based on level
|
|
153
|
+
logger.send(AlertLevel.INFO, "Info message") # goes to #general
|
|
154
|
+
logger.send(AlertLevel.WARN, "Warning message") # goes to #warnings
|
|
155
|
+
logger.send(AlertLevel.ERROR, "Error message") # goes to #alerts
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Custom Channel Resolver
|
|
159
|
+
|
|
160
|
+
You can implement custom channel resolution logic:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
class CustomResolver(ChannelResolver):
|
|
164
|
+
def resolve_channel(self, level):
|
|
165
|
+
if level == AlertLevel.ERROR:
|
|
166
|
+
return "#critical-alerts"
|
|
167
|
+
elif level == AlertLevel.WARN:
|
|
168
|
+
return "#monitoring"
|
|
169
|
+
else:
|
|
170
|
+
return "#general"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Configuration Options
|
|
174
|
+
|
|
175
|
+
### Common Settings
|
|
176
|
+
|
|
177
|
+
- **provider**: `"slack"` or `"lark"`
|
|
178
|
+
- **send_method**: `"webclient"` (token-based authentication)
|
|
179
|
+
- **channel**: Target channel or chat ID (used if no resolver)
|
|
180
|
+
- **channel_resolver**: Optional resolver for dynamic channel mapping
|
|
181
|
+
- **service_name**: Name of the service sending alerts
|
|
182
|
+
- **environment**: Environment (dev, staging, production)
|
|
183
|
+
- **debug**: `True` to enable detailed debug logging of all internal processes
|
|
184
|
+
|
|
185
|
+
### Provider-Specific
|
|
186
|
+
|
|
187
|
+
- **token**: API token for WebClient authentication (required)
|
|
188
|
+
|
|
189
|
+
## Alert Levels
|
|
190
|
+
|
|
191
|
+
- **INFO**: Logs locally only
|
|
192
|
+
- **WARN**: Logs + sends alert
|
|
193
|
+
- **ERROR**: Always sends alert
|
|
194
|
+
|
|
195
|
+
## File Attachments
|
|
196
|
+
|
|
197
|
+
Provide a public URL. The library appends it to the message for simplicity.
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
attachment = Attachment(url="https://example.com/log.txt")
|
|
201
|
+
logger.send(AlertLevel.ERROR, "Error with log", attachment)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Trace Log Section
|
|
205
|
+
|
|
206
|
+
When `include_trace` is set to `True`, you can pass trace information as the fourth parameter to `send()`:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
trace = """Traceback (most recent call last):
|
|
210
|
+
File "app.py", line 10, in main
|
|
211
|
+
raise ValueError("Something went wrong")
|
|
212
|
+
ValueError: Something went wrong"""
|
|
213
|
+
|
|
214
|
+
logger.send(AlertLevel.ERROR, "System error occurred", None, trace)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
This will format the trace as a code block in the alert message.
|
|
218
|
+
|
|
219
|
+
## Testing
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
cd python
|
|
223
|
+
PYTHONPATH=.. python -m unittest test_commonlog.py
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## API Reference
|
|
227
|
+
|
|
228
|
+
### Classes
|
|
229
|
+
|
|
230
|
+
- `Config`: Configuration class
|
|
231
|
+
- `Attachment`: File attachment class
|
|
232
|
+
- `Provider`: Abstract base class for alert providers
|
|
233
|
+
- `commonlog`: Main logger class
|
|
234
|
+
|
|
235
|
+
### Constants
|
|
236
|
+
|
|
237
|
+
- `SendMethod.WEBCLIENT`: Send method (token-based authentication)
|
|
238
|
+
- `AlertLevel.INFO`, `AlertLevel.WARN`, `AlertLevel.ERROR`: Alert levels
|
|
239
|
+
|
|
240
|
+
### Methods
|
|
241
|
+
|
|
242
|
+
- `commonlog(config)`: Create a new logger
|
|
243
|
+
- `commonlog.send(level, message, attachment=None, trace="")`: Send alert with optional trace
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pycommonlog
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
def get_latest_git_tag():
|
|
5
|
+
try:
|
|
6
|
+
tag = subprocess.check_output(["git", "describe", "--tags", "--abbrev=0"]).decode().strip()
|
|
7
|
+
# Clean up invalid version formats
|
|
8
|
+
if '-v' in tag:
|
|
9
|
+
# Extract base version from tags like 0.1.7-v24
|
|
10
|
+
base_version = tag.split('-v')[0]
|
|
11
|
+
return base_version
|
|
12
|
+
return tag
|
|
13
|
+
except Exception:
|
|
14
|
+
return "0.0.0"
|
|
15
|
+
|
|
16
|
+
setup(
|
|
17
|
+
name="pycommonlog",
|
|
18
|
+
version=get_latest_git_tag(),
|
|
19
|
+
description="Unified logging and alerting library for Python.",
|
|
20
|
+
long_description=open("README.md").read(),
|
|
21
|
+
long_description_content_type="text/markdown",
|
|
22
|
+
author="Alvian Rahman Hanif",
|
|
23
|
+
author_email="alvian.hanif@pasarpolis.com",
|
|
24
|
+
url="https://github.com/alvianhanif/pycommonlog",
|
|
25
|
+
packages=["pycommonlog"],
|
|
26
|
+
install_requires=[],
|
|
27
|
+
license="MIT",
|
|
28
|
+
python_requires=">=3.8",
|
|
29
|
+
classifiers=[
|
|
30
|
+
"Programming Language :: Python :: 3",
|
|
31
|
+
"License :: OSI Approved :: MIT License",
|
|
32
|
+
"Operating System :: OS Independent",
|
|
33
|
+
],
|
|
34
|
+
include_package_data=True,
|
|
35
|
+
)
|