|
| 1 | +# Deployment |
| 2 | + |
| 3 | +How the SDK behaves in real process models: servers that fork, workers that |
| 4 | +exec, build steps, and processes with no DSN configured. Everything on this |
| 5 | +page is a contract the SDK keeps, not an implementation detail. |
| 6 | + |
| 7 | +## How initialization happens |
| 8 | + |
| 9 | +There are three ways a process gets its client, in order of coverage: |
| 10 | + |
| 11 | +| Mode | Instrumentation coverage | Use when | |
| 12 | +|---|---|---| |
| 13 | +| `wildedge run -- <cmd>` | Guaranteed: patches before any user code runs | You control the start command | |
| 14 | +| `wildedge.init(...)` at startup | Everything created after the call | You control application code | |
| 15 | +| Lazy, from the first module-level call | Best effort: only what is created afterwards | Manual tracking only | |
| 16 | + |
| 17 | +All three produce the same thing: one process-wide default client that |
| 18 | +`wildedge.span()`, `wildedge.register_model()` and the rest delegate to. |
| 19 | +`init()` without `dsn` reuses whatever client already exists, so code written |
| 20 | +for in-process init runs unchanged under `wildedge run`. |
| 21 | + |
| 22 | +The lazy mode configures itself from the environment: `WILDEDGE_DSN`, |
| 23 | +`WILDEDGE_INTEGRATIONS`, `WILDEDGE_HUBS`. Unset integration variables mean |
| 24 | +nothing is patched; auto-instrumentation is always opt-in. |
| 25 | + |
| 26 | +## The no-DSN contract |
| 27 | + |
| 28 | +Without `WILDEDGE_DSN`, the client is a no-op: no background threads, no |
| 29 | +network, no patched frameworks, every event dropped. This is a supported mode |
| 30 | +for development and CI, not an error; the SDK logs one INFO line per process |
| 31 | +and stays quiet. Leave the SDK integrated and unset the variable wherever you |
| 32 | +do not want telemetry (local dev, test runs, build and migration steps). |
| 33 | + |
| 34 | +Under `wildedge run`, a missing DSN warns on stderr and your program runs |
| 35 | +untracked. Telemetry failing must not take production down; that is the |
| 36 | +default policy. |
| 37 | + |
| 38 | +## Strict mode |
| 39 | + |
| 40 | +`wildedge run --strict` inverts the failure policy for deployments where |
| 41 | +running unobserved is worse than not running. Bootstrap failures then |
| 42 | +terminate the process before your program starts: |
| 43 | + |
| 44 | +| Exit code | Meaning | |
| 45 | +|---|---| |
| 46 | +| 120 | Configuration error (missing or invalid DSN) | |
| 47 | +| 121 | A requested integration could not be instrumented (requires `--strict-integrations`) | |
| 48 | +| 122 | Internal bootstrap error | |
| 49 | + |
| 50 | +`--strict-integrations` is its own opt-in and exits with 121 on failure even |
| 51 | +without `--strict`; asking for it is the request to fail. |
| 52 | + |
| 53 | +## Forking and multi-process servers |
| 54 | + |
| 55 | +`wildedge run` prepends `wildedge/autoload/` to `PYTHONPATH` and replaces |
| 56 | +itself with your command. Every Python interpreter that starts under it runs |
| 57 | +the bundled `sitecustomize.py`, which bootstraps the runtime before any user |
| 58 | +code. Two mechanisms make this safe across worker models: |
| 59 | + |
| 60 | +- Fresh interpreters (exec or spawn) bootstrap themselves via sitecustomize; |
| 61 | + a `sys.modules` marker prevents double initialization within one |
| 62 | + interpreter. |
| 63 | +- Forked children inherit the parent's initialized runtime, and |
| 64 | + `os.register_at_fork` hooks stop the SDK's background threads before each |
| 65 | + fork and start fresh ones in parent and child afterward, so forked workers |
| 66 | + transmit normally instead of inheriting dead threads. |
| 67 | + |
| 68 | +How that plays out on common servers: |
| 69 | + |
| 70 | +| Server | Worker model | Behavior under `wildedge run` | |
| 71 | +|---|---|---| |
| 72 | +| gunicorn (sync/gthread, with or without `preload_app`) | fork from master | Master bootstraps once; each worker gets fresh SDK threads via the at-fork hooks | |
| 73 | +| uvicorn (`--workers`, `--reload`) | spawn / exec | Every worker is a fresh interpreter and bootstraps itself | |
| 74 | +| granian | spawned worker processes | Each worker bootstraps itself | |
| 75 | +| daphne | single process | One bootstrap, nothing special | |
| 76 | +| waitress | single process, thread pool | One bootstrap; the client is thread-safe | |
| 77 | +| celery (prefork pool) | fork from master | Same as gunicorn: at-fork hooks restart threads per worker | |
| 78 | + |
| 79 | +For managed platforms, wrap only the serving command. Build steps and |
| 80 | +migrations run in separate processes that gain nothing from telemetry; leave |
| 81 | +them unwrapped or unset `WILDEDGE_DSN` there. |
| 82 | + |
| 83 | +## One client per process |
| 84 | + |
| 85 | +- Framework patches are installed at most once per process; the first client |
| 86 | + to instrument owns the patch. |
| 87 | +- `init()` reuses the existing default client unless you pass `dsn` |
| 88 | + explicitly, so the CLI-installed client and application code share one |
| 89 | + client instead of racing. |
| 90 | +- Trace and span correlation lives in contextvars at module level, not on a |
| 91 | + client. Auto-instrumented events emitted inside `wildedge.trace(...)` / |
| 92 | + `wildedge.span(...)` blocks correlate into the same trace no matter which |
| 93 | + client emits them. |
| 94 | + |
| 95 | +## Verifying a deployment |
| 96 | + |
| 97 | +`wildedge doctor` answers "will events actually flow from this machine" |
| 98 | +before you rely on it: |
| 99 | + |
| 100 | +```bash |
| 101 | +wildedge doctor --integrations all --send-test-event --format json |
| 102 | +``` |
| 103 | + |
| 104 | +- `--send-test-event` sends one real span event through the full pipeline |
| 105 | + (DSN auth, ingest endpoint, batch protocol) and reports the server's |
| 106 | + response, which a TCP-level `--network-check` cannot do. |
| 107 | +- The report includes an `environment` section with every `WILDEDGE_*` |
| 108 | + variable the runtime reads, plus whether the autoload dir is on |
| 109 | + `PYTHONPATH`. |
| 110 | +- Exit codes: 0 all pass, 1 configuration or dependency failure, 2 the |
| 111 | + config is fine but the ingest endpoint is unreachable or rejecting. |
| 112 | + |
| 113 | +`--format json` makes the output machine-readable; pasting it into an issue |
| 114 | +or an AI assistant is the intended debugging flow. |
| 115 | + |
| 116 | +## Environment propagation |
| 117 | + |
| 118 | +By default, `wildedge run` leaves its `WILDEDGE_*` variables in the |
| 119 | +environment so that exec'd children (reload workers) can bootstrap. Pass |
| 120 | +`--no-propagate` to have each bootstrapped process scrub the run-scoped |
| 121 | +variables after initialization, keeping them away from nested subprocesses |
| 122 | +you spawn yourself. |
0 commit comments