insider-python 0.1.3__tar.gz → 0.1.4__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.
Files changed (55) hide show
  1. {insider_python-0.1.3 → insider_python-0.1.4}/PKG-INFO +71 -29
  2. insider_python-0.1.4/README.md +146 -0
  3. {insider_python-0.1.3 → insider_python-0.1.4}/pyproject.toml +1 -1
  4. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/__init__.py +7 -0
  5. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/_envelope.py +2 -2
  6. insider_python-0.1.4/src/insider/_footprint.py +64 -0
  7. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/_version.py +1 -1
  8. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/client.py +298 -4
  9. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/integrations/__init__.py +4 -0
  10. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/integrations/django/__init__.py +12 -2
  11. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/integrations/django/capture.py +12 -10
  12. insider_python-0.1.4/src/insider/integrations/django/handler.py +71 -0
  13. insider_python-0.1.4/src/insider/integrations/django/perf.py +38 -0
  14. insider_python-0.1.4/src/insider/integrations/logging/__init__.py +66 -0
  15. insider_python-0.1.4/src/insider/integrations/logging/handler.py +74 -0
  16. insider_python-0.1.4/src/insider/integrations/logging/levels.py +29 -0
  17. insider_python-0.1.4/src/insider/scope.py +195 -0
  18. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider_python.egg-info/PKG-INFO +71 -29
  19. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider_python.egg-info/SOURCES.txt +8 -0
  20. insider_python-0.1.4/tests/test_capture_log.py +41 -0
  21. insider_python-0.1.4/tests/test_capture_perf.py +60 -0
  22. insider_python-0.1.4/tests/test_django.py +93 -0
  23. {insider_python-0.1.3 → insider_python-0.1.4}/tests/test_django_integration.py +29 -11
  24. {insider_python-0.1.3 → insider_python-0.1.4}/tests/test_drf_integration.py +16 -12
  25. insider_python-0.1.4/tests/test_logging_integration.py +97 -0
  26. insider_python-0.1.3/README.md +0 -104
  27. insider_python-0.1.3/src/insider/integrations/django/handler.py +0 -42
  28. insider_python-0.1.3/src/insider/scope.py +0 -54
  29. insider_python-0.1.3/tests/test_django.py +0 -101
  30. {insider_python-0.1.3 → insider_python-0.1.4}/setup.cfg +0 -0
  31. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/contrib/__init__.py +0 -0
  32. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/contrib/django/__init__.py +0 -0
  33. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/contrib/django/apps.py +0 -0
  34. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/contrib/django/middleware.py +0 -0
  35. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/dsn.py +0 -0
  36. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/integrations/django/drf.py +0 -0
  37. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/integrations/django/request.py +0 -0
  38. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/integrations/django/signals.py +0 -0
  39. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/integrations/django/wsgi.py +0 -0
  40. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/py.typed +0 -0
  41. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/safety.py +0 -0
  42. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/scrubbing.py +0 -0
  43. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/stacktrace.py +0 -0
  44. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider/transport.py +0 -0
  45. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider_python.egg-info/dependency_links.txt +0 -0
  46. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider_python.egg-info/requires.txt +0 -0
  47. {insider_python-0.1.3 → insider_python-0.1.4}/src/insider_python.egg-info/top_level.txt +0 -0
  48. {insider_python-0.1.3 → insider_python-0.1.4}/tests/test_capture.py +0 -0
  49. {insider_python-0.1.3 → insider_python-0.1.4}/tests/test_dsn.py +0 -0
  50. {insider_python-0.1.3 → insider_python-0.1.4}/tests/test_envelope.py +0 -0
  51. {insider_python-0.1.3 → insider_python-0.1.4}/tests/test_never_crash.py +0 -0
  52. {insider_python-0.1.3 → insider_python-0.1.4}/tests/test_safety.py +0 -0
  53. {insider_python-0.1.3 → insider_python-0.1.4}/tests/test_scrubbing.py +0 -0
  54. {insider_python-0.1.3 → insider_python-0.1.4}/tests/test_stacktrace.py +0 -0
  55. {insider_python-0.1.3 → insider_python-0.1.4}/tests/test_transport.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: insider-python
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Python SDK for Insider — ship Beacons to your Insider server.
5
5
  Author: Insider
6
6
  License-Expression: MIT
@@ -63,10 +63,11 @@ try:
63
63
  risky()
64
64
  except Exception as exc:
65
65
  insider.capture_exception(exc)
66
-
67
- insider.capture_message("cache miss spiked", level="warning")
68
66
  ```
69
67
 
68
+ Out-of-band events (background jobs, explicit calls) use standalone beacons:
69
+ `capture_exception`, `capture_log`, `capture_perf`.
70
+
70
71
  ### Django
71
72
 
72
73
  Initialize in `wsgi.py` (or `asgi.py`) before `get_wsgi_application()`:
@@ -75,57 +76,98 @@ Initialize in `wsgi.py` (or `asgi.py`) before `get_wsgi_application()`:
75
76
  import os
76
77
  import insider
77
78
  from insider.integrations.django import DjangoIntegration
79
+ from insider.integrations.logging import LoggingIntegration
78
80
 
79
81
  insider.init(
80
82
  dsn=os.environ.get("INSIDER_DSN"),
81
83
  environment="production",
82
84
  release="1.2.3",
83
- integrations=[DjangoIntegration()],
85
+ enable_logs=True,
86
+ integrations=[DjangoIntegration(), LoggingIntegration()],
84
87
  )
85
88
 
86
89
  from django.core.wsgi import get_wsgi_application
87
90
  application = get_wsgi_application()
88
91
  ```
89
92
 
90
- That's the whole setup. Every unhandled exception in a view — including
91
- Django REST Framework API views — is now a Beacon in your dashboard.
93
+ That's the whole setup. **Every HTTP request** emits **one** `kind=request`
94
+ beacon containing:
95
+
96
+ - timing (duration, method, path, status)
97
+ - request context (headers, path, query string)
98
+ - stdlib logs during that request (when `enable_logs=True`)
99
+ - unhandled exception + stack trace (when the request fails)
100
+
92
101
  No middleware, no `INSTALLED_APPS`, and no `EXCEPTION_HANDLER` wiring.
93
102
 
94
- **Legacy setup** (still supported): add `insider.contrib.django` to
95
- `INSTALLED_APPS` and `InsiderMiddleware` to `MIDDLEWARE`. Prefer the
96
- `wsgi.py` pattern for new projects.
103
+ Disable auto capture on high-traffic apps until sampling lands:
97
104
 
98
- ## Configuration
105
+ ```python
106
+ integrations=[DjangoIntegration(auto_perf=False)]
107
+ ```
108
+
109
+ ### Logging
110
+
111
+ During an HTTP request, stdlib `logging` lines are **buffered into the
112
+ request envelope** — not beamed as separate rows:
113
+
114
+ ```python
115
+ import logging
116
+
117
+ logger = logging.getLogger(__name__)
118
+ logger.info("checkout completed") # → payload.logs[] on the request beacon
119
+ ```
99
120
 
100
- Order of precedence (first wins):
121
+ Requires `enable_logs=True`, `LoggingIntegration()`, and a configured
122
+ logger level (see your app's `LOGGING` settings).
101
123
 
102
- 1. Keyword args to `insider.init(...)`.
103
- 2. `INSIDER_*` environment variables.
104
- 3. Django settings (when the Django integration is active).
105
- 4. Hard-coded defaults.
124
+ Outside an HTTP request, stdlib logs still beam as standalone `kind=log`
125
+ beacons. For explicit structured events, use `capture_log()`:
126
+
127
+ ```python
128
+ insider.capture_log(
129
+ "User checkout completed",
130
+ level="info",
131
+ source="checkout.service",
132
+ tags={"feature": "checkout"},
133
+ )
134
+ ```
135
+
136
+ Manual perf timings (Celery, custom spans):
137
+
138
+ ```python
139
+ insider.capture_perf(
140
+ op="celery.tasks.send_email",
141
+ duration_ms=842,
142
+ )
143
+ ```
144
+
145
+ ## Footprint kinds
146
+
147
+ | Kind | When |
148
+ |------|------|
149
+ | `request` | One per HTTP request (DjangoIntegration) |
150
+ | `error` | Manual `capture_exception()` outside request cycle |
151
+ | `log` | Manual `capture_log()` or stdlib logs outside request cycle |
152
+ | `perf` | Manual `capture_perf()` for non-HTTP timings |
153
+
154
+ ## Configuration
106
155
 
107
156
  If no DSN is found anywhere, the SDK enters **disabled mode**: every
108
- public call is a no-op, no thread starts, no socket opens.
157
+ public call is a no-op.
109
158
 
110
159
  | Option | Default | Notes |
111
160
  |--------|---------|-------|
112
161
  | `dsn` | env `INSIDER_DSN` | If absent, SDK is disabled |
113
- | `environment` | `"production"` | Top-level Beacon field |
114
- | `release` | `None` | Top-level Beacon field |
115
- | `send_default_pii` | `False` | Required to capture `user.id`, request bodies |
116
- | `before_send` | `None` | `(beacon) -> beacon | None` hook |
117
- | `scrub_keys` | `None` | Extra keys to filter (added to the default deny-list) |
118
- | `in_app_include` | `None` | Filename prefixes considered "your code" |
119
- | `transport_queue_size` | `1000` | Bounded; drops on overflow |
120
- | `transport_flush_timeout` | `2.0` | Seconds. Used by `close()` / `flush()` |
121
- | `debug` | `False` | Print SDK's own warnings to stderr |
162
+ | `environment` | `"production"` | Top-level Footprint field |
163
+ | `release` | `None` | Top-level Footprint field |
164
+ | `enable_logs` | `False` | Buffer stdlib logs into request envelopes |
165
+ | `send_default_pii` | `False` | Required to capture request bodies |
166
+ | `debug` | `False` | Print SDK warnings to stderr |
122
167
 
123
168
  ## Promise
124
169
 
125
- The SDK never raises into your code. Every public function catches
126
- `Exception` at its boundary; if something goes wrong inside the SDK,
127
- you get nothing back and a debug log (if enabled). Your app keeps
128
- running.
170
+ The SDK never raises into your code.
129
171
 
130
172
  ## License
131
173
 
@@ -0,0 +1,146 @@
1
+ # insider-python
2
+
3
+ The Python SDK for [Insider](https://insider.moraks.cloud/).
4
+
5
+ Beam Beacons from your Python service to your Insider server with a
6
+ one-line setup. No runtime overhead on your request path. Never raises
7
+ into your code, no matter what.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install insider-python
13
+ ```
14
+
15
+ For the Django integration:
16
+
17
+ ```bash
18
+ pip install "insider-python[django]"
19
+ ```
20
+
21
+ ## Quick start
22
+
23
+ ### Plain Python
24
+
25
+ ```python
26
+ import insider
27
+
28
+ insider.init(
29
+ dsn="https://<beacon_token>@insider.example.com/<project_uuid>",
30
+ environment="production",
31
+ release="1.2.3",
32
+ )
33
+
34
+ try:
35
+ risky()
36
+ except Exception as exc:
37
+ insider.capture_exception(exc)
38
+ ```
39
+
40
+ Out-of-band events (background jobs, explicit calls) use standalone beacons:
41
+ `capture_exception`, `capture_log`, `capture_perf`.
42
+
43
+ ### Django
44
+
45
+ Initialize in `wsgi.py` (or `asgi.py`) before `get_wsgi_application()`:
46
+
47
+ ```python
48
+ import os
49
+ import insider
50
+ from insider.integrations.django import DjangoIntegration
51
+ from insider.integrations.logging import LoggingIntegration
52
+
53
+ insider.init(
54
+ dsn=os.environ.get("INSIDER_DSN"),
55
+ environment="production",
56
+ release="1.2.3",
57
+ enable_logs=True,
58
+ integrations=[DjangoIntegration(), LoggingIntegration()],
59
+ )
60
+
61
+ from django.core.wsgi import get_wsgi_application
62
+ application = get_wsgi_application()
63
+ ```
64
+
65
+ That's the whole setup. **Every HTTP request** emits **one** `kind=request`
66
+ beacon containing:
67
+
68
+ - timing (duration, method, path, status)
69
+ - request context (headers, path, query string)
70
+ - stdlib logs during that request (when `enable_logs=True`)
71
+ - unhandled exception + stack trace (when the request fails)
72
+
73
+ No middleware, no `INSTALLED_APPS`, and no `EXCEPTION_HANDLER` wiring.
74
+
75
+ Disable auto capture on high-traffic apps until sampling lands:
76
+
77
+ ```python
78
+ integrations=[DjangoIntegration(auto_perf=False)]
79
+ ```
80
+
81
+ ### Logging
82
+
83
+ During an HTTP request, stdlib `logging` lines are **buffered into the
84
+ request envelope** — not beamed as separate rows:
85
+
86
+ ```python
87
+ import logging
88
+
89
+ logger = logging.getLogger(__name__)
90
+ logger.info("checkout completed") # → payload.logs[] on the request beacon
91
+ ```
92
+
93
+ Requires `enable_logs=True`, `LoggingIntegration()`, and a configured
94
+ logger level (see your app's `LOGGING` settings).
95
+
96
+ Outside an HTTP request, stdlib logs still beam as standalone `kind=log`
97
+ beacons. For explicit structured events, use `capture_log()`:
98
+
99
+ ```python
100
+ insider.capture_log(
101
+ "User checkout completed",
102
+ level="info",
103
+ source="checkout.service",
104
+ tags={"feature": "checkout"},
105
+ )
106
+ ```
107
+
108
+ Manual perf timings (Celery, custom spans):
109
+
110
+ ```python
111
+ insider.capture_perf(
112
+ op="celery.tasks.send_email",
113
+ duration_ms=842,
114
+ )
115
+ ```
116
+
117
+ ## Footprint kinds
118
+
119
+ | Kind | When |
120
+ |------|------|
121
+ | `request` | One per HTTP request (DjangoIntegration) |
122
+ | `error` | Manual `capture_exception()` outside request cycle |
123
+ | `log` | Manual `capture_log()` or stdlib logs outside request cycle |
124
+ | `perf` | Manual `capture_perf()` for non-HTTP timings |
125
+
126
+ ## Configuration
127
+
128
+ If no DSN is found anywhere, the SDK enters **disabled mode**: every
129
+ public call is a no-op.
130
+
131
+ | Option | Default | Notes |
132
+ |--------|---------|-------|
133
+ | `dsn` | env `INSIDER_DSN` | If absent, SDK is disabled |
134
+ | `environment` | `"production"` | Top-level Footprint field |
135
+ | `release` | `None` | Top-level Footprint field |
136
+ | `enable_logs` | `False` | Buffer stdlib logs into request envelopes |
137
+ | `send_default_pii` | `False` | Required to capture request bodies |
138
+ | `debug` | `False` | Print SDK warnings to stderr |
139
+
140
+ ## Promise
141
+
142
+ The SDK never raises into your code.
143
+
144
+ ## License
145
+
146
+ MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "insider-python"
7
- version = "0.1.3"
7
+ version = "0.1.4"
8
8
  description = "Python SDK for Insider — ship Beacons to your Insider server."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -6,6 +6,7 @@ Public API:
6
6
  insider.init(dsn=..., environment=..., release=..., ...)
7
7
  insider.capture_exception(exc, level="error", tags=..., extra=...)
8
8
  insider.capture_message("text", level="info", tags=..., extra=...)
9
+ insider.capture_perf("GET /api/users/", duration_ms=45, status_code=200)
9
10
  insider.flush(timeout=2.0)
10
11
  insider.close(timeout=2.0)
11
12
 
@@ -17,12 +18,15 @@ from ._version import __version__
17
18
  from .client import (
18
19
  Client,
19
20
  capture_exception,
21
+ capture_log,
20
22
  capture_message,
23
+ capture_perf,
21
24
  close,
22
25
  flush,
23
26
  init,
24
27
  )
25
28
  from .dsn import DSN, InvalidDSNError
29
+ from .integrations.logging import LoggingIntegration
26
30
 
27
31
  __all__ = [
28
32
  "Client",
@@ -30,8 +34,11 @@ __all__ = [
30
34
  "InvalidDSNError",
31
35
  "__version__",
32
36
  "capture_exception",
37
+ "capture_log",
33
38
  "capture_message",
39
+ "capture_perf",
34
40
  "close",
35
41
  "flush",
36
42
  "init",
43
+ "LoggingIntegration",
37
44
  ]
@@ -1,5 +1,5 @@
1
1
  """
2
- Beacon envelope construction + size-budget enforcement.
2
+ Footprint envelope construction + size-budget enforcement.
3
3
 
4
4
  `build_envelope` is called from the capture functions in `client.py`. It
5
5
  takes the raw bits (kind, level, message, exception payload, scope,
@@ -58,7 +58,7 @@ def build_envelope(
58
58
  occurred_at: Optional[str] = None,
59
59
  commit_hash: Optional[str] = None,
60
60
  ) -> Dict[str, Any]:
61
- """Assemble the Beacon envelope. Pure: no I/O, no globals."""
61
+ """Assemble the Footprint envelope. Pure: no I/O, no globals."""
62
62
  body: Dict[str, Any] = dict(payload or {})
63
63
  if tags:
64
64
  body["tags"] = tags
@@ -0,0 +1,64 @@
1
+ """Build flat footprint payloads for beam ingest."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, Optional
6
+
7
+ from ._version import __version__
8
+ from .stacktrace import runtime_payload
9
+
10
+
11
+ def build_footprint_payload(
12
+ *,
13
+ request_id: Optional[str],
14
+ request_path: str,
15
+ request_method: Optional[str],
16
+ request_user: str = "anonymous",
17
+ request_body: Any = None,
18
+ response_body: Any = None,
19
+ response_time: float,
20
+ status_code: int,
21
+ system_logs: Optional[list] = None,
22
+ ip_address: Optional[str] = None,
23
+ user_agent: Optional[str] = None,
24
+ db_query_count: int = 0,
25
+ exception_block: Optional[Dict[str, Any]] = None,
26
+ environment: str = "production",
27
+ release: Optional[str] = None,
28
+ service_name: Optional[str] = None,
29
+ commit_hash: Optional[str] = None,
30
+ ) -> Dict[str, Any]:
31
+ runtime = runtime_payload(__version__)
32
+ stack_trace = None
33
+ exception_name = None
34
+ if exception_block:
35
+ exception_name = exception_block.get("type")
36
+ stack_trace = dict(exception_block)
37
+ if commit_hash:
38
+ stack_trace["commit_hash"] = commit_hash
39
+
40
+ body = request_body
41
+ if body is not None and not isinstance(body, (dict, list, str, int, float, bool)):
42
+ body = str(body)
43
+
44
+ return {
45
+ "request_id": request_id,
46
+ "request_user": request_user,
47
+ "request_path": request_path,
48
+ "request_body": body if body is not None else None,
49
+ "request_method": (request_method or "").lower() or None,
50
+ "response_body": response_body,
51
+ "response_time": float(response_time),
52
+ "status_code": status_code,
53
+ "system_logs": system_logs,
54
+ "ip_address": ip_address,
55
+ "user_agent": user_agent,
56
+ "db_query_count": db_query_count,
57
+ "exception_name": exception_name,
58
+ "stack_trace": stack_trace,
59
+ "service_name": service_name,
60
+ "environment": environment,
61
+ "language": runtime.get("language"),
62
+ "framework": runtime.get("framework"),
63
+ "release": release,
64
+ }
@@ -5,4 +5,4 @@ lookup on every beacon. Bump this and `[project].version` together when
5
5
  cutting a release.
6
6
  """
7
7
 
8
- __version__ = "0.1.3"
8
+ __version__ = "0.1.4"