auditry 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- auditry-0.1.0/LICENSE +21 -0
- auditry-0.1.0/MANIFEST.in +3 -0
- auditry-0.1.0/PKG-INFO +392 -0
- auditry-0.1.0/README.md +364 -0
- auditry-0.1.0/pyproject.toml +46 -0
- auditry-0.1.0/setup.cfg +4 -0
- auditry-0.1.0/src/auditry/__init__.py +19 -0
- auditry-0.1.0/src/auditry/correlation.py +21 -0
- auditry-0.1.0/src/auditry/logging_config.py +63 -0
- auditry-0.1.0/src/auditry/middleware.py +355 -0
- auditry-0.1.0/src/auditry/models.py +71 -0
- auditry-0.1.0/src/auditry/py.typed +0 -0
- auditry-0.1.0/src/auditry/redaction.py +144 -0
- auditry-0.1.0/src/auditry/unified_middleware.py +74 -0
- auditry-0.1.0/src/auditry.egg-info/PKG-INFO +392 -0
- auditry-0.1.0/src/auditry.egg-info/SOURCES.txt +20 -0
- auditry-0.1.0/src/auditry.egg-info/dependency_links.txt +1 -0
- auditry-0.1.0/src/auditry.egg-info/requires.txt +4 -0
- auditry-0.1.0/src/auditry.egg-info/top_level.txt +1 -0
- auditry-0.1.0/tests/test_correlation.py +9 -0
- auditry-0.1.0/tests/test_middleware.py +84 -0
- auditry-0.1.0/tests/test_redaction.py +32 -0
auditry-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Liv Stark
|
|
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.
|
auditry-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: auditry
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: FastAPI middleware for request/response logging, correlation IDs, and sensitive data redaction
|
|
5
|
+
Author-email: Liv Stark <livstark.work@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/lst4rk/auditry
|
|
8
|
+
Project-URL: Repository, https://github.com/lst4rk/auditry
|
|
9
|
+
Project-URL: Issues, https://github.com/lst4rk/auditry/issues
|
|
10
|
+
Keywords: fastapi,observability,logging,middleware,correlation-id,redaction
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Framework :: FastAPI
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Topic :: System :: Logging
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: fastapi>=0.100.0
|
|
25
|
+
Requires-Dist: pydantic>=2.0.0
|
|
26
|
+
Requires-Dist: structlog>=23.0.0
|
|
27
|
+
Requires-Dist: asgi-correlation-id>=4.0.0
|
|
28
|
+
|
|
29
|
+
# auditry
|
|
30
|
+
|
|
31
|
+
FastAPI observability middleware with automatic request/response logging, correlation IDs, and sensitive data redaction.
|
|
32
|
+
|
|
33
|
+
[](https://badge.fury.io/py/auditry)
|
|
34
|
+
[](https://pypi.org/project/auditry/)
|
|
35
|
+
[](https://opensource.org/licenses/MIT)
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install auditry
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from fastapi import FastAPI
|
|
47
|
+
from auditry import configure_logging, ObservabilityMiddleware, ObservabilityConfig
|
|
48
|
+
from auditry import get_logger
|
|
49
|
+
|
|
50
|
+
# Configure structured logging at startup
|
|
51
|
+
configure_logging(level="INFO")
|
|
52
|
+
|
|
53
|
+
app = FastAPI()
|
|
54
|
+
|
|
55
|
+
# Add observability middleware (single line!)
|
|
56
|
+
app.add_middleware(
|
|
57
|
+
ObservabilityMiddleware,
|
|
58
|
+
config=ObservabilityConfig(
|
|
59
|
+
service_name="my-service-name",
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
logger = get_logger(__name__) # always use the get_logger from this package
|
|
64
|
+
|
|
65
|
+
@app.get("/")
|
|
66
|
+
async def root():
|
|
67
|
+
|
|
68
|
+
logger.info("Hello World")
|
|
69
|
+
|
|
70
|
+
return {"message": "Hello World"}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
### Required Configuration
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
config = ObservabilityConfig(
|
|
79
|
+
service_name="your-service-name", # REQUIRED
|
|
80
|
+
)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Full Configuration Options
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
config = ObservabilityConfig(
|
|
87
|
+
# REQUIRED: Service name for log filtering
|
|
88
|
+
service_name="my-service-name",
|
|
89
|
+
|
|
90
|
+
# Correlation ID header name (default: X-Correlation-ID)
|
|
91
|
+
# Use this if your org uses a different header, such as X-Request-ID
|
|
92
|
+
correlation_id_header="X-Correlation-ID",
|
|
93
|
+
|
|
94
|
+
# Maximum request/response body size to log (default: 10KB)
|
|
95
|
+
payload_size_limit=10_240,
|
|
96
|
+
|
|
97
|
+
# Additional sensitive field patterns to redact
|
|
98
|
+
additional_redaction_patterns=["internal_id", "employee_ssn"],
|
|
99
|
+
|
|
100
|
+
# Whether to log request headers (default: True)
|
|
101
|
+
log_request_headers=True,
|
|
102
|
+
|
|
103
|
+
# Whether to log response headers (default: False)
|
|
104
|
+
log_response_headers=False,
|
|
105
|
+
|
|
106
|
+
# Whether to log query parameters (default: True)
|
|
107
|
+
log_query_params=True,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
app.add_middleware(ObservabilityMiddleware, config=config)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Correlation IDs
|
|
114
|
+
|
|
115
|
+
Correlation IDs are automatically handled:
|
|
116
|
+
|
|
117
|
+
- **Incoming requests**: Extracts from `X-Correlation-ID` header (or your custom header)
|
|
118
|
+
- **Generated if missing**: Creates a new UUID if no correlation ID provided
|
|
119
|
+
- **Added to response**: Returns the correlation ID in the response header
|
|
120
|
+
- **Included in logs**: Automatically included in all structured logs
|
|
121
|
+
|
|
122
|
+
### Using Correlation IDs in Your Code
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from auditry import get_logger, get_correlation_id
|
|
126
|
+
|
|
127
|
+
logger = get_logger(__name__)
|
|
128
|
+
|
|
129
|
+
@app.get("/users/{user_id}")
|
|
130
|
+
async def get_user(user_id: str):
|
|
131
|
+
# Correlation ID is automatically available
|
|
132
|
+
correlation_id = get_correlation_id()
|
|
133
|
+
|
|
134
|
+
# All logs automatically include the correlation ID
|
|
135
|
+
logger.info(f"Fetching user {user_id}")
|
|
136
|
+
|
|
137
|
+
return {"user_id": user_id, "correlation_id": correlation_id}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Propagating to Downstream Services
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
import httpx
|
|
144
|
+
from auditry import get_correlation_id
|
|
145
|
+
|
|
146
|
+
@app.get("/proxy")
|
|
147
|
+
async def proxy_request():
|
|
148
|
+
# Get the current correlation ID
|
|
149
|
+
correlation_id = get_correlation_id()
|
|
150
|
+
|
|
151
|
+
# Pass it to downstream services
|
|
152
|
+
async with httpx.AsyncClient() as client:
|
|
153
|
+
response = await client.get(
|
|
154
|
+
"https://downstream-service.com/api/data",
|
|
155
|
+
headers={"X-Correlation-ID": correlation_id} # Use your org's header name
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return response.json()
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## User Tracking
|
|
162
|
+
|
|
163
|
+
The middleware automatically extracts user IDs from FastAPI dependencies and includes them in logs.
|
|
164
|
+
|
|
165
|
+
### Supported User Patterns
|
|
166
|
+
|
|
167
|
+
The middleware automatically detects user IDs from these patterns:
|
|
168
|
+
|
|
169
|
+
1. **Object with `id` attribute**: `current_user.id`
|
|
170
|
+
2. **Object with `user_id` attribute**: `current_user.user_id`
|
|
171
|
+
3. **Dict with `id` key**: `user["id"]`
|
|
172
|
+
4. **Dict with `user_id` key**: `user["user_id"]`
|
|
173
|
+
5. **Dict with `sub` key**: `user["sub"]` (JWT standard)
|
|
174
|
+
6. **Request state**: `request.state.user_id`
|
|
175
|
+
|
|
176
|
+
**You don't need to modify any existing code** - the middleware automatically finds the user ID!
|
|
177
|
+
|
|
178
|
+
## Business Event Tagging (For Analytics)
|
|
179
|
+
|
|
180
|
+
Tag specific endpoints as "business events" to make analytics queries easier for your sales/product teams.
|
|
181
|
+
|
|
182
|
+
### Configuration
|
|
183
|
+
|
|
184
|
+
Tag endpoints in your middleware config - zero code changes needed in your actual endpoints:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from auditry import ObservabilityMiddleware, ObservabilityConfig, BusinessEventConfig
|
|
188
|
+
|
|
189
|
+
app.add_middleware(
|
|
190
|
+
ObservabilityMiddleware,
|
|
191
|
+
config=ObservabilityConfig(
|
|
192
|
+
service_name="my-service-name",
|
|
193
|
+
|
|
194
|
+
# Define which endpoints to tag for analytics
|
|
195
|
+
business_events={
|
|
196
|
+
"POST /workflows": BusinessEventConfig(
|
|
197
|
+
event_type="workflow.created",
|
|
198
|
+
extract_from_request=["file_id"], # Pull file_id from request body
|
|
199
|
+
extract_from_response=["id"], # Pull workflow id from response
|
|
200
|
+
),
|
|
201
|
+
"DELETE /workflows/{workflow_id}": BusinessEventConfig(
|
|
202
|
+
event_type="workflow.deleted",
|
|
203
|
+
extract_from_path=["workflow_id"], # Pull workflow_id from URL path
|
|
204
|
+
),
|
|
205
|
+
},
|
|
206
|
+
),
|
|
207
|
+
)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Log Output with Event Tags
|
|
211
|
+
|
|
212
|
+
Regular log (no tagging):
|
|
213
|
+
```json
|
|
214
|
+
{
|
|
215
|
+
"service": "my-service-name",
|
|
216
|
+
"message": "Request completed: POST /workflows - Status: 201",
|
|
217
|
+
"request": {...},
|
|
218
|
+
"response": {...}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Tagged business event log:
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"service": "my-service-name",
|
|
226
|
+
"message": "Request completed: POST /workflows - Status: 201",
|
|
227
|
+
"event_type": "workflow.created", // ← Filterable in log platform
|
|
228
|
+
"business_context": {
|
|
229
|
+
"file_id": "file_123", // ← From request body
|
|
230
|
+
"id": "workflow_789" // ← From response body
|
|
231
|
+
},
|
|
232
|
+
"request": {...},
|
|
233
|
+
"response": {...}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Supported Extract Locations
|
|
238
|
+
|
|
239
|
+
- `extract_from_request`: Fields from request JSON body
|
|
240
|
+
- `extract_from_response`: Fields from response JSON body
|
|
241
|
+
- `extract_from_path`: Parameters from URL path (e.g., `/workflows/{workflow_id}`)
|
|
242
|
+
|
|
243
|
+
## Log Output
|
|
244
|
+
|
|
245
|
+
All logs are structured JSON, ready for log aggregators:
|
|
246
|
+
|
|
247
|
+
```json
|
|
248
|
+
{
|
|
249
|
+
"timestamp": "2025-10-28T12:34:56.789012+00:00",
|
|
250
|
+
"level": "INFO",
|
|
251
|
+
"service": "my-service-name",
|
|
252
|
+
"correlation_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
253
|
+
"message": "Request completed: POST /workflows - Status: 201 - Duration: 45.23ms",
|
|
254
|
+
"request": {
|
|
255
|
+
"method": "POST",
|
|
256
|
+
"path": "/workflows",
|
|
257
|
+
"query_params": {},
|
|
258
|
+
"headers": {"user-agent": "curl/7.64.1", "authorization": "[REDACTED]"},
|
|
259
|
+
"body": {"name": "My Workflow", "password": "[REDACTED]"},
|
|
260
|
+
"user_id": "user_12345"
|
|
261
|
+
},
|
|
262
|
+
"response": {
|
|
263
|
+
"status_code": 201,
|
|
264
|
+
"duration_ms": 45.23,
|
|
265
|
+
"body": {"id": "workflow_789", "name": "My Workflow"}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Sensitive Data Redaction
|
|
271
|
+
|
|
272
|
+
Automatically redacts these sensitive field patterns:
|
|
273
|
+
|
|
274
|
+
- `password`
|
|
275
|
+
- `token`
|
|
276
|
+
- `api_key` / `apikey`
|
|
277
|
+
- `secret`
|
|
278
|
+
- `authorization`
|
|
279
|
+
- `ssn` / `social_security_number`
|
|
280
|
+
- `credit_card` / `creditcard`
|
|
281
|
+
- `x-api-key`
|
|
282
|
+
|
|
283
|
+
Add custom patterns via configuration:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
config = ObservabilityConfig(
|
|
287
|
+
service_name="my-service-name",
|
|
288
|
+
additional_redaction_patterns=["internal_token", "employee_id"],
|
|
289
|
+
)
|
|
290
|
+
```
|
|
291
|
+
## Best Practices
|
|
292
|
+
|
|
293
|
+
### 1. Configure Logging Early
|
|
294
|
+
|
|
295
|
+
Call `configure_logging()` at application startup, before any other code:
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
from auditry import configure_logging
|
|
299
|
+
|
|
300
|
+
# First thing in your app
|
|
301
|
+
configure_logging(level="INFO")
|
|
302
|
+
|
|
303
|
+
app = FastAPI()
|
|
304
|
+
# ... rest of your app
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### 2. Use Structured Logging
|
|
308
|
+
|
|
309
|
+
Always use `get_logger(__name__)` instead of standard Python logging:
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
from auditry import get_logger
|
|
313
|
+
|
|
314
|
+
logger = get_logger(__name__)
|
|
315
|
+
|
|
316
|
+
# Good - structured with correlation ID
|
|
317
|
+
logger.info("Processing payment", amount=100.50, currency="USD")
|
|
318
|
+
|
|
319
|
+
# Bad - loses structured data
|
|
320
|
+
import logging
|
|
321
|
+
logging.info("Processing payment")
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### 3. Propagate Correlation IDs
|
|
325
|
+
|
|
326
|
+
When calling downstream services, always pass the correlation ID:
|
|
327
|
+
|
|
328
|
+
```python
|
|
329
|
+
from auditry import get_correlation_id
|
|
330
|
+
|
|
331
|
+
correlation_id = get_correlation_id()
|
|
332
|
+
headers = {"X-Correlation-ID": correlation_id} # Use your org's header name
|
|
333
|
+
response = await client.get(url, headers=headers)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### 4. Customize for Your Organization
|
|
337
|
+
|
|
338
|
+
Match your org's conventions:
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
config = ObservabilityConfig(
|
|
342
|
+
service_name="my-service-name",
|
|
343
|
+
correlation_id_header="X-Request-ID", # If your org uses this header instead
|
|
344
|
+
additional_redaction_patterns=["ssn", "tax_id"], # Your sensitive fields
|
|
345
|
+
)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Example Output: Success vs Failure
|
|
349
|
+
|
|
350
|
+
### Successful Request
|
|
351
|
+
|
|
352
|
+
```json
|
|
353
|
+
{
|
|
354
|
+
"level": "INFO",
|
|
355
|
+
"service": "my-service-name",
|
|
356
|
+
"correlation_id": "abc-123",
|
|
357
|
+
"message": "Request completed: POST /workflows - Status: 201 - Duration: 45ms",
|
|
358
|
+
"request": {...},
|
|
359
|
+
"response": {...}
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Failed Request
|
|
364
|
+
|
|
365
|
+
```json
|
|
366
|
+
{
|
|
367
|
+
"level": "ERROR",
|
|
368
|
+
"service": "my-service-name",
|
|
369
|
+
"correlation_id": "abc-123",
|
|
370
|
+
"message": "Request failed: POST /workflows - Error: ValueError: Invalid name",
|
|
371
|
+
"request": {...},
|
|
372
|
+
"exception_type": "ValueError",
|
|
373
|
+
"exception_message": "Invalid name",
|
|
374
|
+
"execution_duration_ms": 12.34
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## License
|
|
379
|
+
|
|
380
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
381
|
+
|
|
382
|
+
## Contributing
|
|
383
|
+
|
|
384
|
+
Contributions welcome! Please submit a Pull Request.
|
|
385
|
+
|
|
386
|
+
## Support
|
|
387
|
+
|
|
388
|
+
For issues and questions: [GitHub Issues](https://github.com/lst4rk/auditry/issues)
|
|
389
|
+
|
|
390
|
+
## Author
|
|
391
|
+
|
|
392
|
+
**Liv Stark** - [livstark.work@gmail.com](mailto:livstark.work@gmail.com)
|