secploy 0.2.4__tar.gz → 0.2.6__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.
- {secploy-0.2.4 → secploy-0.2.6}/PKG-INFO +154 -31
- secploy-0.2.4/secploy.egg-info/PKG-INFO → secploy-0.2.6/README.md +130 -37
- secploy-0.2.6/secploy/__init__.py +47 -0
- secploy-0.2.6/secploy/client.py +166 -0
- {secploy-0.2.4 → secploy-0.2.6}/secploy/enums.py +2 -1
- secploy-0.2.6/secploy/events.py +40 -0
- secploy-0.2.6/secploy/lib/__init__.py +11 -0
- secploy-0.2.6/secploy/lib/config.py +204 -0
- secploy-0.2.6/secploy/lib/secploy_logger.py +45 -0
- secploy-0.2.6/secploy/log_capture.py +169 -0
- secploy-0.2.6/secploy/processor.py +120 -0
- secploy-0.2.6/secploy/schemas.py +49 -0
- secploy-0.2.4/README.md → secploy-0.2.6/secploy.egg-info/PKG-INFO +160 -4
- {secploy-0.2.4 → secploy-0.2.6}/secploy.egg-info/SOURCES.txt +9 -11
- secploy-0.2.6/secploy.egg-info/entry_points.txt +2 -0
- secploy-0.2.6/secploy.egg-info/requires.txt +3 -0
- secploy-0.2.6/setup.py +33 -0
- secploy-0.2.4/pyproject.toml +0 -19
- secploy-0.2.4/secploy/__init__.py +0 -7
- secploy-0.2.4/secploy/client.py +0 -210
- secploy-0.2.4/secploy/schema.py +0 -0
- secploy-0.2.4/secploy/schemas.py +0 -20
- secploy-0.2.4/secploy/utils.py +0 -5
- secploy-0.2.4/secploy.egg-info/requires.txt +0 -1
- {secploy-0.2.4 → secploy-0.2.6}/LICENSE +0 -0
- {secploy-0.2.4 → secploy-0.2.6}/MANIFEST.in +0 -0
- /secploy-0.2.4/secploy/exceptions.py → /secploy-0.2.6/secploy/utils.py +0 -0
- {secploy-0.2.4 → secploy-0.2.6}/secploy.egg-info/dependency_links.txt +0 -0
- {secploy-0.2.4 → secploy-0.2.6}/secploy.egg-info/top_level.txt +0 -0
- {secploy-0.2.4 → secploy-0.2.6}/setup.cfg +0 -0
|
@@ -1,35 +1,32 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: secploy
|
|
3
|
-
Version: 0.2.
|
|
4
|
-
Summary:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in all
|
|
18
|
-
copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
-
SOFTWARE.
|
|
27
|
-
|
|
28
|
-
Requires-Python: >=3.7
|
|
3
|
+
Version: 0.2.6
|
|
4
|
+
Summary: Event tracking and monitoring SDK for Python applications
|
|
5
|
+
Home-page: https://github.com/agastronics/secploy-python-sdk
|
|
6
|
+
Author: Agastronics
|
|
7
|
+
Author-email: support@agastronics.com
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
29
16
|
Description-Content-Type: text/markdown
|
|
30
17
|
License-File: LICENSE
|
|
31
|
-
Requires-Dist: requests>=2.
|
|
18
|
+
Requires-Dist: requests>=2.25.0
|
|
19
|
+
Requires-Dist: pyyaml>=5.1
|
|
20
|
+
Requires-Dist: pydantic>=2.0.0
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: author-email
|
|
23
|
+
Dynamic: classifier
|
|
24
|
+
Dynamic: description
|
|
25
|
+
Dynamic: description-content-type
|
|
26
|
+
Dynamic: home-page
|
|
32
27
|
Dynamic: license-file
|
|
28
|
+
Dynamic: requires-dist
|
|
29
|
+
Dynamic: summary
|
|
33
30
|
|
|
34
31
|
<p align="center">
|
|
35
32
|
<img src="https://secploy.vercel.app/logo.png" alt="Secploy Logo" width="180">
|
|
@@ -59,26 +56,31 @@ The Secploy SDK can be configured in multiple ways, providing flexibility for di
|
|
|
59
56
|
client = SecployClient()
|
|
60
57
|
|
|
61
58
|
config = SecployConfig(
|
|
62
|
-
api_key="your-key",
|
|
59
|
+
api_key="your-project-key",
|
|
60
|
+
environment_key="your-key"
|
|
61
|
+
organization_id="your-organization-id",
|
|
63
62
|
environment="production",
|
|
64
63
|
log_level=LogLevel.INFO,
|
|
65
64
|
batch_size=100
|
|
66
65
|
)
|
|
67
66
|
|
|
68
67
|
client = SecployClient(config=config)
|
|
69
|
-
|
|
68
|
+
```
|
|
70
69
|
|
|
71
70
|
2. **Configuration file** (.secploy or project-name.secploy)
|
|
72
71
|
|
|
73
72
|
```yaml
|
|
74
|
-
api_key: your-api-key
|
|
73
|
+
api_key: your-project-api-key
|
|
74
|
+
environment_key: key
|
|
75
|
+
organization_id: "your-organization-id",
|
|
75
76
|
environment: production
|
|
76
77
|
debug: true
|
|
77
|
-
|
|
78
|
+
```
|
|
78
79
|
|
|
79
80
|
3. **Environment variables**
|
|
80
81
|
```bash
|
|
81
82
|
export SECPLOY_API_KEY=your-api-key
|
|
83
|
+
export SECPLOY_ORGANIZATION_ID=your-organization-id
|
|
82
84
|
export SECPLOY_ENVIRONMENT=production
|
|
83
85
|
export SECPLOY_DEBUG=true
|
|
84
86
|
```
|
|
@@ -88,6 +90,7 @@ debug: true
|
|
|
88
90
|
| Option | Type | Default | Description |
|
|
89
91
|
| -------------------- | ---------------- | --------------------------- | ----------------------------------------------------- |
|
|
90
92
|
| `api_key` | `str` | Required | Your Secploy project API key |
|
|
93
|
+
| `environment_key` | `str` | Required | Your Secploy environment API key |
|
|
91
94
|
| `environment` | `str` | `"development"` | Environment name (e.g., production, staging) |
|
|
92
95
|
| `ingest_url` | `str` | `"https://api.secploy.com"` | Secploy API endpoint |
|
|
93
96
|
| `heartbeat_interval` | `int` | `30` | Seconds between heartbeat signals |
|
|
@@ -109,6 +112,7 @@ Create a `.secploy` file in your project root:
|
|
|
109
112
|
```yaml
|
|
110
113
|
# Required settings
|
|
111
114
|
api_key: your-api-key
|
|
115
|
+
environment_key: key
|
|
112
116
|
environment: production
|
|
113
117
|
|
|
114
118
|
# Event batching
|
|
@@ -139,6 +143,7 @@ All configuration options can be set via environment variables with the `SECPLOY
|
|
|
139
143
|
```bash
|
|
140
144
|
# Required settings
|
|
141
145
|
SECPLOY_API_KEY=your-api-key
|
|
146
|
+
SECPLOY_ENVIRONMENT_KEY=key
|
|
142
147
|
SECPLOY_ENVIRONMENT=production
|
|
143
148
|
|
|
144
149
|
# Event batching
|
|
@@ -286,6 +291,124 @@ Each environment has its own **API key** — use the matching key for the enviro
|
|
|
286
291
|
| `heartbeat()` | Send a heartbeat signal |
|
|
287
292
|
| `listen_status()` | Stream live project status |
|
|
288
293
|
| `set_environment(env_code)` | Switch environment dynamically |
|
|
294
|
+
| `capture_logs(loggers, level)` | Start capturing logs |
|
|
295
|
+
| `stop_capturing_logs(loggers)` | Stop capturing specific logs |
|
|
296
|
+
|
|
297
|
+
## 📝 Structured Logging
|
|
298
|
+
|
|
299
|
+
Secploy provides powerful structured logging capabilities that automatically format and send your logs with rich context.
|
|
300
|
+
|
|
301
|
+
### Basic Log Capture
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
import logging
|
|
305
|
+
from secploy import SecployClient
|
|
306
|
+
|
|
307
|
+
# Initialize client
|
|
308
|
+
client = SecployClient()
|
|
309
|
+
client.start()
|
|
310
|
+
|
|
311
|
+
# Start capturing logs
|
|
312
|
+
client.capture_logs(['your_app'], level=logging.INFO)
|
|
313
|
+
|
|
314
|
+
# Your regular logging calls will now be captured
|
|
315
|
+
logger = logging.getLogger('your_app')
|
|
316
|
+
logger.info("User logged in", extra={
|
|
317
|
+
'user_id': 'user123',
|
|
318
|
+
'login_method': 'oauth'
|
|
319
|
+
})
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Log Schema
|
|
323
|
+
|
|
324
|
+
All captured logs are automatically structured in a consistent format:
|
|
325
|
+
|
|
326
|
+
```json
|
|
327
|
+
{
|
|
328
|
+
"timestamp": "2025-09-13T16:45:00Z",
|
|
329
|
+
"type": "error",
|
|
330
|
+
"message": "Unhandled exception in payment processor",
|
|
331
|
+
"context": {
|
|
332
|
+
"user_id": "usr_12345",
|
|
333
|
+
"session_id": "sess_abcd",
|
|
334
|
+
"http_method": "POST",
|
|
335
|
+
"http_url": "/api/payments/charge",
|
|
336
|
+
"http_status": 500,
|
|
337
|
+
"stacktrace": [
|
|
338
|
+
"File \"payment_service.py\", line 42, in process_charge",
|
|
339
|
+
"File \"stripe_gateway.py\", line 87, in create_charge",
|
|
340
|
+
"Exception: Card declined"
|
|
341
|
+
],
|
|
342
|
+
"tags": {
|
|
343
|
+
"environment": "production",
|
|
344
|
+
"service": "payments",
|
|
345
|
+
"region": "us-east-1"
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Example with Flask Application
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
import logging
|
|
355
|
+
from flask import Flask, request
|
|
356
|
+
from secploy import SecployClient
|
|
357
|
+
|
|
358
|
+
# Set up logging
|
|
359
|
+
logger = logging.getLogger(__name__)
|
|
360
|
+
|
|
361
|
+
# Initialize Flask and Secploy
|
|
362
|
+
app = Flask(__name__)
|
|
363
|
+
client = SecployClient()
|
|
364
|
+
client.start()
|
|
365
|
+
|
|
366
|
+
# Capture logs from all components
|
|
367
|
+
client.capture_logs([
|
|
368
|
+
'flask.app', # Flask framework logs
|
|
369
|
+
__name__, # Main application logs
|
|
370
|
+
'payment_processor' # Component-specific logs
|
|
371
|
+
], level=logging.INFO)
|
|
372
|
+
|
|
373
|
+
@app.route('/api/payment', methods=['POST'])
|
|
374
|
+
def process_payment():
|
|
375
|
+
try:
|
|
376
|
+
data = request.json
|
|
377
|
+
logger.info("Processing payment", extra={
|
|
378
|
+
'user_id': data.get('user_id'),
|
|
379
|
+
'http_method': request.method,
|
|
380
|
+
'http_url': request.path,
|
|
381
|
+
'amount': data.get('amount')
|
|
382
|
+
})
|
|
383
|
+
# Process payment...
|
|
384
|
+
return {"status": "success"}, 200
|
|
385
|
+
except Exception as e:
|
|
386
|
+
logger.error(
|
|
387
|
+
"Payment failed",
|
|
388
|
+
exc_info=True, # This captures the stack trace
|
|
389
|
+
extra={
|
|
390
|
+
'user_id': data.get('user_id'),
|
|
391
|
+
'http_method': request.method,
|
|
392
|
+
'http_url': request.path,
|
|
393
|
+
'http_status': 500
|
|
394
|
+
}
|
|
395
|
+
)
|
|
396
|
+
return {"error": str(e)}, 500
|
|
397
|
+
|
|
398
|
+
if __name__ == '__main__':
|
|
399
|
+
try:
|
|
400
|
+
app.run()
|
|
401
|
+
finally:
|
|
402
|
+
client.stop()
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Best Practices for Logging
|
|
406
|
+
|
|
407
|
+
1. **Add Context**: Always include relevant context using the `extra` parameter
|
|
408
|
+
2. **Use Proper Log Levels**: Choose appropriate levels (DEBUG, INFO, WARNING, ERROR)
|
|
409
|
+
3. **Include Stack Traces**: Use `exc_info=True` when logging exceptions
|
|
410
|
+
4. **Capture All Components**: Include all relevant loggers in your capture list
|
|
411
|
+
5. **Start Early, Stop Late**: Initialize logging before your app starts and stop it in a finally block
|
|
289
412
|
|
|
290
413
|
---
|
|
291
414
|
|
|
@@ -1,36 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: secploy
|
|
3
|
-
Version: 0.2.4
|
|
4
|
-
Summary: Python SDK for Secploy security monitoring and event tracking
|
|
5
|
-
Author-email: Abdulsamad Abdulganiyu <agastronics@gmail.com>
|
|
6
|
-
License: MIT License
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2025 AGASTRONICS
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in all
|
|
18
|
-
copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
-
SOFTWARE.
|
|
27
|
-
|
|
28
|
-
Requires-Python: >=3.7
|
|
29
|
-
Description-Content-Type: text/markdown
|
|
30
|
-
License-File: LICENSE
|
|
31
|
-
Requires-Dist: requests>=2.20.0
|
|
32
|
-
Dynamic: license-file
|
|
33
|
-
|
|
34
1
|
<p align="center">
|
|
35
2
|
<img src="https://secploy.vercel.app/logo.png" alt="Secploy Logo" width="180">
|
|
36
3
|
</p>
|
|
@@ -59,26 +26,31 @@ The Secploy SDK can be configured in multiple ways, providing flexibility for di
|
|
|
59
26
|
client = SecployClient()
|
|
60
27
|
|
|
61
28
|
config = SecployConfig(
|
|
62
|
-
api_key="your-key",
|
|
29
|
+
api_key="your-project-key",
|
|
30
|
+
environment_key="your-key"
|
|
31
|
+
organization_id="your-organization-id",
|
|
63
32
|
environment="production",
|
|
64
33
|
log_level=LogLevel.INFO,
|
|
65
34
|
batch_size=100
|
|
66
35
|
)
|
|
67
36
|
|
|
68
37
|
client = SecployClient(config=config)
|
|
69
|
-
|
|
38
|
+
```
|
|
70
39
|
|
|
71
40
|
2. **Configuration file** (.secploy or project-name.secploy)
|
|
72
41
|
|
|
73
42
|
```yaml
|
|
74
|
-
api_key: your-api-key
|
|
43
|
+
api_key: your-project-api-key
|
|
44
|
+
environment_key: key
|
|
45
|
+
organization_id: "your-organization-id",
|
|
75
46
|
environment: production
|
|
76
47
|
debug: true
|
|
77
|
-
|
|
48
|
+
```
|
|
78
49
|
|
|
79
50
|
3. **Environment variables**
|
|
80
51
|
```bash
|
|
81
52
|
export SECPLOY_API_KEY=your-api-key
|
|
53
|
+
export SECPLOY_ORGANIZATION_ID=your-organization-id
|
|
82
54
|
export SECPLOY_ENVIRONMENT=production
|
|
83
55
|
export SECPLOY_DEBUG=true
|
|
84
56
|
```
|
|
@@ -88,6 +60,7 @@ debug: true
|
|
|
88
60
|
| Option | Type | Default | Description |
|
|
89
61
|
| -------------------- | ---------------- | --------------------------- | ----------------------------------------------------- |
|
|
90
62
|
| `api_key` | `str` | Required | Your Secploy project API key |
|
|
63
|
+
| `environment_key` | `str` | Required | Your Secploy environment API key |
|
|
91
64
|
| `environment` | `str` | `"development"` | Environment name (e.g., production, staging) |
|
|
92
65
|
| `ingest_url` | `str` | `"https://api.secploy.com"` | Secploy API endpoint |
|
|
93
66
|
| `heartbeat_interval` | `int` | `30` | Seconds between heartbeat signals |
|
|
@@ -109,6 +82,7 @@ Create a `.secploy` file in your project root:
|
|
|
109
82
|
```yaml
|
|
110
83
|
# Required settings
|
|
111
84
|
api_key: your-api-key
|
|
85
|
+
environment_key: key
|
|
112
86
|
environment: production
|
|
113
87
|
|
|
114
88
|
# Event batching
|
|
@@ -139,6 +113,7 @@ All configuration options can be set via environment variables with the `SECPLOY
|
|
|
139
113
|
```bash
|
|
140
114
|
# Required settings
|
|
141
115
|
SECPLOY_API_KEY=your-api-key
|
|
116
|
+
SECPLOY_ENVIRONMENT_KEY=key
|
|
142
117
|
SECPLOY_ENVIRONMENT=production
|
|
143
118
|
|
|
144
119
|
# Event batching
|
|
@@ -286,6 +261,124 @@ Each environment has its own **API key** — use the matching key for the enviro
|
|
|
286
261
|
| `heartbeat()` | Send a heartbeat signal |
|
|
287
262
|
| `listen_status()` | Stream live project status |
|
|
288
263
|
| `set_environment(env_code)` | Switch environment dynamically |
|
|
264
|
+
| `capture_logs(loggers, level)` | Start capturing logs |
|
|
265
|
+
| `stop_capturing_logs(loggers)` | Stop capturing specific logs |
|
|
266
|
+
|
|
267
|
+
## 📝 Structured Logging
|
|
268
|
+
|
|
269
|
+
Secploy provides powerful structured logging capabilities that automatically format and send your logs with rich context.
|
|
270
|
+
|
|
271
|
+
### Basic Log Capture
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
import logging
|
|
275
|
+
from secploy import SecployClient
|
|
276
|
+
|
|
277
|
+
# Initialize client
|
|
278
|
+
client = SecployClient()
|
|
279
|
+
client.start()
|
|
280
|
+
|
|
281
|
+
# Start capturing logs
|
|
282
|
+
client.capture_logs(['your_app'], level=logging.INFO)
|
|
283
|
+
|
|
284
|
+
# Your regular logging calls will now be captured
|
|
285
|
+
logger = logging.getLogger('your_app')
|
|
286
|
+
logger.info("User logged in", extra={
|
|
287
|
+
'user_id': 'user123',
|
|
288
|
+
'login_method': 'oauth'
|
|
289
|
+
})
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Log Schema
|
|
293
|
+
|
|
294
|
+
All captured logs are automatically structured in a consistent format:
|
|
295
|
+
|
|
296
|
+
```json
|
|
297
|
+
{
|
|
298
|
+
"timestamp": "2025-09-13T16:45:00Z",
|
|
299
|
+
"type": "error",
|
|
300
|
+
"message": "Unhandled exception in payment processor",
|
|
301
|
+
"context": {
|
|
302
|
+
"user_id": "usr_12345",
|
|
303
|
+
"session_id": "sess_abcd",
|
|
304
|
+
"http_method": "POST",
|
|
305
|
+
"http_url": "/api/payments/charge",
|
|
306
|
+
"http_status": 500,
|
|
307
|
+
"stacktrace": [
|
|
308
|
+
"File \"payment_service.py\", line 42, in process_charge",
|
|
309
|
+
"File \"stripe_gateway.py\", line 87, in create_charge",
|
|
310
|
+
"Exception: Card declined"
|
|
311
|
+
],
|
|
312
|
+
"tags": {
|
|
313
|
+
"environment": "production",
|
|
314
|
+
"service": "payments",
|
|
315
|
+
"region": "us-east-1"
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Example with Flask Application
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
import logging
|
|
325
|
+
from flask import Flask, request
|
|
326
|
+
from secploy import SecployClient
|
|
327
|
+
|
|
328
|
+
# Set up logging
|
|
329
|
+
logger = logging.getLogger(__name__)
|
|
330
|
+
|
|
331
|
+
# Initialize Flask and Secploy
|
|
332
|
+
app = Flask(__name__)
|
|
333
|
+
client = SecployClient()
|
|
334
|
+
client.start()
|
|
335
|
+
|
|
336
|
+
# Capture logs from all components
|
|
337
|
+
client.capture_logs([
|
|
338
|
+
'flask.app', # Flask framework logs
|
|
339
|
+
__name__, # Main application logs
|
|
340
|
+
'payment_processor' # Component-specific logs
|
|
341
|
+
], level=logging.INFO)
|
|
342
|
+
|
|
343
|
+
@app.route('/api/payment', methods=['POST'])
|
|
344
|
+
def process_payment():
|
|
345
|
+
try:
|
|
346
|
+
data = request.json
|
|
347
|
+
logger.info("Processing payment", extra={
|
|
348
|
+
'user_id': data.get('user_id'),
|
|
349
|
+
'http_method': request.method,
|
|
350
|
+
'http_url': request.path,
|
|
351
|
+
'amount': data.get('amount')
|
|
352
|
+
})
|
|
353
|
+
# Process payment...
|
|
354
|
+
return {"status": "success"}, 200
|
|
355
|
+
except Exception as e:
|
|
356
|
+
logger.error(
|
|
357
|
+
"Payment failed",
|
|
358
|
+
exc_info=True, # This captures the stack trace
|
|
359
|
+
extra={
|
|
360
|
+
'user_id': data.get('user_id'),
|
|
361
|
+
'http_method': request.method,
|
|
362
|
+
'http_url': request.path,
|
|
363
|
+
'http_status': 500
|
|
364
|
+
}
|
|
365
|
+
)
|
|
366
|
+
return {"error": str(e)}, 500
|
|
367
|
+
|
|
368
|
+
if __name__ == '__main__':
|
|
369
|
+
try:
|
|
370
|
+
app.run()
|
|
371
|
+
finally:
|
|
372
|
+
client.stop()
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Best Practices for Logging
|
|
376
|
+
|
|
377
|
+
1. **Add Context**: Always include relevant context using the `extra` parameter
|
|
378
|
+
2. **Use Proper Log Levels**: Choose appropriate levels (DEBUG, INFO, WARNING, ERROR)
|
|
379
|
+
3. **Include Stack Traces**: Use `exc_info=True` when logging exceptions
|
|
380
|
+
4. **Capture All Components**: Include all relevant loggers in your capture list
|
|
381
|
+
5. **Start Early, Stop Late**: Initialize logging before your app starts and stop it in a finally block
|
|
289
382
|
|
|
290
383
|
---
|
|
291
384
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Secploy SDK - Event tracking and monitoring for Python applications
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .client import SecployClient
|
|
6
|
+
from .schemas import SecployConfig
|
|
7
|
+
from .schemas import LogLevel
|
|
8
|
+
|
|
9
|
+
__version__ = "0.2.5"
|
|
10
|
+
__author__ = "Agastronics"
|
|
11
|
+
__email__ = "support@agastronics.com"
|
|
12
|
+
__description__ = "Event tracking and monitoring SDK for Python applications"
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"SecployClient",
|
|
16
|
+
"SecployConfig",
|
|
17
|
+
"LogLevel",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
def cli():
|
|
21
|
+
"""Command-line interface for the Secploy SDK."""
|
|
22
|
+
import argparse
|
|
23
|
+
|
|
24
|
+
parser = argparse.ArgumentParser(description=__description__)
|
|
25
|
+
parser.add_argument('--version', action='version', version=f'Secploy SDK v{__version__}')
|
|
26
|
+
parser.add_argument('--test-config', help='Test a configuration file', metavar='CONFIG_FILE')
|
|
27
|
+
|
|
28
|
+
args = parser.parse_args()
|
|
29
|
+
|
|
30
|
+
if args.test_config:
|
|
31
|
+
from .lib.config import load_config
|
|
32
|
+
try:
|
|
33
|
+
config = load_config(args.test_config)
|
|
34
|
+
print("Configuration loaded successfully:")
|
|
35
|
+
for key, value in config.items():
|
|
36
|
+
print(f" {key}: {value}")
|
|
37
|
+
except Exception as e:
|
|
38
|
+
print(f"Error loading configuration: {e}")
|
|
39
|
+
return 1
|
|
40
|
+
else:
|
|
41
|
+
parser.print_help()
|
|
42
|
+
|
|
43
|
+
return 0
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
import sys
|
|
47
|
+
sys.exit(cli())
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any, Dict, Optional, List, Union
|
|
4
|
+
import queue
|
|
5
|
+
|
|
6
|
+
from .lib import setup_logger, load_config, DEFAULT_CONFIG, secploy_logger
|
|
7
|
+
from .schemas import SecployConfig, LogLevel
|
|
8
|
+
from .log_capture import SecployLogCapturer
|
|
9
|
+
from .events import EventHandler
|
|
10
|
+
from .processor import EventProcessor
|
|
11
|
+
|
|
12
|
+
class SecployClient:
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
api_key: Optional[str] = None,
|
|
16
|
+
environment_key: Optional[str] = None,
|
|
17
|
+
organization_id: Optional[str] = None,
|
|
18
|
+
config_file: Optional[str] = None,
|
|
19
|
+
config: Optional[SecployConfig] = DEFAULT_CONFIG,
|
|
20
|
+
log_levels: Optional[List[Union[str, int]]] = None
|
|
21
|
+
):
|
|
22
|
+
"""
|
|
23
|
+
Initialize the Secploy client.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
api_key: Optional API key to override configuration
|
|
27
|
+
environment_key: Optional Environment key to override configuration
|
|
28
|
+
organization_id: Optional Organization ID to override configuration
|
|
29
|
+
config_file: Optional path to configuration file
|
|
30
|
+
config: Configuration object, defaults to DEFAULT_CONFIG
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
ValueError: If required configuration is missing
|
|
34
|
+
TypeError: If configuration values have invalid types
|
|
35
|
+
"""
|
|
36
|
+
# Load config from file if provided else it will load from default locations or find .secploy
|
|
37
|
+
config = load_config(config_file)
|
|
38
|
+
|
|
39
|
+
if config is None:
|
|
40
|
+
secploy_logger.error("No valid configuration found")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
# Override api_key if provided directly
|
|
44
|
+
if api_key:
|
|
45
|
+
config.api_key = api_key
|
|
46
|
+
if environment_key:
|
|
47
|
+
config.environment_key = environment_key
|
|
48
|
+
if organization_id:
|
|
49
|
+
config.organization_id = organization_id
|
|
50
|
+
|
|
51
|
+
# Special handling for log_level if it's a string
|
|
52
|
+
if isinstance(config.get('log_level'), str):
|
|
53
|
+
try:
|
|
54
|
+
config['log_level'] = LogLevel(config['log_level'].upper())
|
|
55
|
+
except ValueError:
|
|
56
|
+
raise ValueError(
|
|
57
|
+
f"Invalid log level: {config.get('log_level')}. Must be one of: "
|
|
58
|
+
f"{', '.join(level.value for level in LogLevel)}"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Validate required fields
|
|
62
|
+
if not config.get('api_key'):
|
|
63
|
+
raise ValueError("API key is required")
|
|
64
|
+
if not config.get('environment_key'):
|
|
65
|
+
raise ValueError("Environment key is required")
|
|
66
|
+
if not config.get('organization_id'):
|
|
67
|
+
raise ValueError("Organization ID is required")
|
|
68
|
+
if not config.get('ingest_url'):
|
|
69
|
+
raise ValueError("Ingest URL is required")
|
|
70
|
+
|
|
71
|
+
# Set instance attributes from config
|
|
72
|
+
self.api_key = config['api_key']
|
|
73
|
+
self.environment_key = config.get('environment_key')
|
|
74
|
+
self.organization_id = config.get('organization_id')
|
|
75
|
+
self.environment = config.get('environment', 'development')
|
|
76
|
+
self.sampling_rate = config.get('sampling_rate', 1.0)
|
|
77
|
+
self.ingest_url = config['ingest_url'].rstrip("/")
|
|
78
|
+
self.heartbeat_interval = config.get('heartbeat_interval', 60)
|
|
79
|
+
self.max_retry = config.get('max_retry', 5)
|
|
80
|
+
self.debug = config.get('debug', False)
|
|
81
|
+
self.log_level = config.get('log_level', 'INFO')
|
|
82
|
+
|
|
83
|
+
# Batch processing configuration
|
|
84
|
+
self.batch_size = config.get('batch_size', 100) # Max events per batch
|
|
85
|
+
self.flush_interval = config.get('flush_interval', 60) # Max seconds between flushes
|
|
86
|
+
|
|
87
|
+
# Initialize internal state
|
|
88
|
+
self._event_queue = queue.Queue()
|
|
89
|
+
self._event_handler = EventHandler(self._event_queue)
|
|
90
|
+
|
|
91
|
+
# Initialize event processor
|
|
92
|
+
self._event_processor = EventProcessor(
|
|
93
|
+
queue=self._event_queue,
|
|
94
|
+
ingest_url=self.ingest_url,
|
|
95
|
+
headers_callback=self._headers,
|
|
96
|
+
batch_size=self.batch_size,
|
|
97
|
+
flush_interval=self.flush_interval,
|
|
98
|
+
max_retry=self.max_retry
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Setup logging
|
|
102
|
+
if self.debug:
|
|
103
|
+
setup_logger(log_level=self.log_level)
|
|
104
|
+
|
|
105
|
+
# Initialize log capturer
|
|
106
|
+
self._log_capturer = SecployLogCapturer(self, levels=log_levels)
|
|
107
|
+
|
|
108
|
+
def capture_logs(self, loggers: Union[str, List[str], None] = None):
|
|
109
|
+
"""
|
|
110
|
+
Start capturing logs from specified loggers.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
loggers: Logger name(s) to capture. Can be:
|
|
114
|
+
- None to capture the root logger
|
|
115
|
+
- A string for a single logger
|
|
116
|
+
- A list of logger names
|
|
117
|
+
"""
|
|
118
|
+
self._log_capturer.start_capture(loggers)
|
|
119
|
+
secploy_logger.info(f"Started capturing logs from {loggers or 'root'}")
|
|
120
|
+
|
|
121
|
+
def stop_capturing_logs(self, loggers: Union[str, List[str], None] = None):
|
|
122
|
+
"""
|
|
123
|
+
Stop capturing logs from specified loggers.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
loggers: Logger name(s) to stop capturing
|
|
127
|
+
"""
|
|
128
|
+
self._log_capturer.stop_capture(loggers)
|
|
129
|
+
secploy_logger.info(f"Stopped capturing logs from {loggers or 'root'}")
|
|
130
|
+
|
|
131
|
+
def _headers(self):
|
|
132
|
+
return {
|
|
133
|
+
"X-API-Key": f"{self.api_key}",
|
|
134
|
+
"X-Environment-Key": f"{self.environment_key}",
|
|
135
|
+
"X-Organization-ID": f"{self.organization_id}",
|
|
136
|
+
"Content-Type": "application/json",
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
def send_event(self, event_type: str, payload: Dict[str, Any]) -> bool:
|
|
140
|
+
"""
|
|
141
|
+
Queue an event for sending. Events are batched and sent periodically.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
event_type: Type of the event
|
|
145
|
+
payload: Event payload data
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
bool: True if event was queued successfully
|
|
149
|
+
"""
|
|
150
|
+
return self._event_handler.send_event(event_type, payload)
|
|
151
|
+
|
|
152
|
+
def start(self):
|
|
153
|
+
"""Start the client's event processing."""
|
|
154
|
+
secploy_logger.info("Starting Secploy client...")
|
|
155
|
+
self._event_processor.start()
|
|
156
|
+
|
|
157
|
+
def stop(self):
|
|
158
|
+
"""Stop the client and wait for processing to finish."""
|
|
159
|
+
secploy_logger.info("Stopping Secploy client...")
|
|
160
|
+
|
|
161
|
+
# Stop all log capturing
|
|
162
|
+
if hasattr(self, '_log_capturer'):
|
|
163
|
+
self._log_capturer.stop_all()
|
|
164
|
+
|
|
165
|
+
# Stop event processing
|
|
166
|
+
self._event_processor.stop()
|