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 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.
@@ -0,0 +1,3 @@
1
+ include README.md
2
+ include LICENSE
3
+ include src/auditry/py.typed
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
+ [![PyPI version](https://badge.fury.io/py/auditry.svg)](https://badge.fury.io/py/auditry)
34
+ [![Python Versions](https://img.shields.io/pypi/pyversions/auditry.svg)](https://pypi.org/project/auditry/)
35
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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)