plain.cloud 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,7 @@
1
+ # plain-cloud changelog
2
+
3
+ ## [0.1.0](https://github.com/dropseed/plain/releases/plain-cloud@0.1.0) (2026-04-01)
4
+
5
+ ### What's changed
6
+
7
+ - **Initial release.** Sets up OpenTelemetry TracerProvider and MeterProvider with OTLP HTTP exporters, pushing traces and metrics to Plain Cloud. Configure with `CLOUD_EXPORT_URL` and `CLOUD_EXPORT_TOKEN` settings. Includes head-based trace sampling via `CLOUD_TRACE_SAMPLE_RATE`. Inactive when `CLOUD_EXPORT_URL` is not set. Coexists with plain-observer — observer layers its sampler and span processor on top. ([e3971506cb](https://github.com/dropseed/plain/commit/e3971506cb))
plain/cloud/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # plain.cloud
2
+
3
+ **Production observability via OTLP export to Plain Cloud.**
4
+
5
+ - [Overview](#overview)
6
+ - [Settings](#settings)
7
+ - [Sampling](#sampling)
8
+ - [What gets exported](#what-gets-exported)
9
+ - [Observer coexistence](#observer-coexistence)
10
+ - [FAQs](#faqs)
11
+ - [Installation](#installation)
12
+
13
+ ## Overview
14
+
15
+ You can use plain.cloud to export traces and metrics from your Plain app to Plain Cloud. The framework already instruments itself with OpenTelemetry spans and histograms — plain.cloud activates them by providing the OTLP exporters.
16
+
17
+ Set two environment variables and your app starts pushing telemetry:
18
+
19
+ ```
20
+ PLAIN_CLOUD_EXPORT_URL=https://ingest.plaincloud.com
21
+ PLAIN_CLOUD_EXPORT_TOKEN=your-token
22
+ ```
23
+
24
+ If `CLOUD_EXPORT_URL` is not set, the package is a no-op — safe to install without configuration.
25
+
26
+ ## Settings
27
+
28
+ | Setting | Default | Description |
29
+ | ------------------------- | ------- | ----------------------------------------------------------- |
30
+ | `CLOUD_EXPORT_URL` | `""` | OTLP ingest endpoint (e.g. `https://ingest.plaincloud.com`) |
31
+ | `CLOUD_EXPORT_TOKEN` | `""` | Auth token for the export endpoint |
32
+ | `CLOUD_TRACE_SAMPLE_RATE` | `1.0` | Probability of exporting a trace (0.0–1.0) |
33
+
34
+ All settings can be set via `PLAIN_`-prefixed environment variables or in `app/settings.py`.
35
+
36
+ ## Sampling
37
+
38
+ By default, all traces are exported. To reduce volume, set a sample rate:
39
+
40
+ ```python
41
+ CLOUD_TRACE_SAMPLE_RATE = 0.1 # Export 10% of traces
42
+ ```
43
+
44
+ Metrics are not affected by sampling — histograms aggregate in-process and export periodically regardless of the trace sample rate.
45
+
46
+ ## What gets exported
47
+
48
+ **Traces** — HTTP request spans and database query spans instrumented by the framework.
49
+
50
+ **Metrics** — OTel histograms like `db.client.query.duration`, aggregated and pushed every 60 seconds.
51
+
52
+ ## Observer coexistence
53
+
54
+ If [plain.observer](../../plain-observer/plain/observer/README.md) is also installed, both work simultaneously. plain.cloud handles production export while observer provides the local dev toolbar and admin trace viewer. Observer detects the existing TracerProvider and layers its sampler and span processor on top.
55
+
56
+ ## FAQs
57
+
58
+ #### Do I need plain.observer to use plain.cloud?
59
+
60
+ No. plain.cloud works independently. Observer is for local dev tooling; plain.cloud is for production export.
61
+
62
+ #### What happens if the export endpoint is unreachable?
63
+
64
+ The OTLP exporters batch and retry automatically. If the endpoint is down, telemetry is dropped after retries — it does not block your application.
65
+
66
+ #### Does this add latency to requests?
67
+
68
+ No. Trace spans are exported in a background thread via `BatchSpanProcessor`. Metrics are flushed periodically by a background thread. Neither blocks request handling.
69
+
70
+ ## Installation
71
+
72
+ ```python
73
+ # app/settings.py
74
+ INSTALLED_PACKAGES = [
75
+ "plain.cloud",
76
+ # ...
77
+ ]
78
+ ```
79
+
80
+ Place `plain.cloud` **before** `plain.observer` in `INSTALLED_PACKAGES` so it sets up the TracerProvider first.
File without changes
plain/cloud/config.py ADDED
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ import atexit
4
+
5
+ from opentelemetry import metrics, trace
6
+ from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
7
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
8
+ from opentelemetry.sdk.metrics import MeterProvider
9
+ from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
10
+ from opentelemetry.sdk.resources import Resource
11
+ from opentelemetry.sdk.trace import TracerProvider, sampling
12
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
13
+ from opentelemetry.semconv.attributes import service_attributes
14
+
15
+ from plain.packages import PackageConfig, register_config
16
+ from plain.runtime import settings
17
+
18
+
19
+ @register_config
20
+ class Config(PackageConfig):
21
+ package_label = "plaincloud"
22
+
23
+ def ready(self) -> None:
24
+ if not settings.CLOUD_EXPORT_URL:
25
+ return
26
+
27
+ resource = Resource.create(
28
+ {
29
+ service_attributes.SERVICE_NAME: settings.NAME,
30
+ service_attributes.SERVICE_VERSION: settings.VERSION,
31
+ }
32
+ )
33
+
34
+ export_url = str(settings.CLOUD_EXPORT_URL).rstrip("/")
35
+ headers = {"Authorization": f"Bearer {settings.CLOUD_EXPORT_TOKEN}"}
36
+
37
+ # Traces
38
+ span_exporter = OTLPSpanExporter(
39
+ endpoint=f"{export_url}/v1/traces",
40
+ headers=headers,
41
+ )
42
+ sampler = sampling.TraceIdRatioBased(settings.CLOUD_TRACE_SAMPLE_RATE)
43
+ tracer_provider = TracerProvider(sampler=sampler, resource=resource)
44
+ tracer_provider.add_span_processor(BatchSpanProcessor(span_exporter))
45
+ trace.set_tracer_provider(tracer_provider)
46
+
47
+ # Metrics
48
+ metric_exporter = OTLPMetricExporter(
49
+ endpoint=f"{export_url}/v1/metrics",
50
+ headers=headers,
51
+ )
52
+ reader = PeriodicExportingMetricReader(metric_exporter)
53
+ meter_provider = MeterProvider(metric_readers=[reader], resource=resource)
54
+ metrics.set_meter_provider(meter_provider)
55
+
56
+ atexit.register(tracer_provider.shutdown)
57
+ atexit.register(meter_provider.shutdown)
@@ -0,0 +1,5 @@
1
+ from plain.runtime import Secret
2
+
3
+ CLOUD_EXPORT_URL: str = "" # e.g. "https://ingest.plaincloud.com"
4
+ CLOUD_EXPORT_TOKEN: Secret[str] = "" # Auth token for the export endpoint
5
+ CLOUD_TRACE_SAMPLE_RATE: float = 1.0 # 0.0–1.0, probability of exporting a trace
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.4
2
+ Name: plain.cloud
3
+ Version: 0.1.0
4
+ Summary: Production observability via OTLP export to Plain Cloud.
5
+ Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
+ License-Expression: BSD-3-Clause
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.13
9
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.34.1
10
+ Requires-Dist: opentelemetry-sdk>=1.34.1
11
+ Requires-Dist: plain<1.0.0,>=0.113.0
12
+ Description-Content-Type: text/markdown
13
+
14
+ # plain.cloud
15
+
16
+ **Production observability via OTLP export to Plain Cloud.**
17
+
18
+ - [Overview](#overview)
19
+ - [Settings](#settings)
20
+ - [Sampling](#sampling)
21
+ - [What gets exported](#what-gets-exported)
22
+ - [Observer coexistence](#observer-coexistence)
23
+ - [FAQs](#faqs)
24
+ - [Installation](#installation)
25
+
26
+ ## Overview
27
+
28
+ You can use plain.cloud to export traces and metrics from your Plain app to Plain Cloud. The framework already instruments itself with OpenTelemetry spans and histograms — plain.cloud activates them by providing the OTLP exporters.
29
+
30
+ Set two environment variables and your app starts pushing telemetry:
31
+
32
+ ```
33
+ PLAIN_CLOUD_EXPORT_URL=https://ingest.plaincloud.com
34
+ PLAIN_CLOUD_EXPORT_TOKEN=your-token
35
+ ```
36
+
37
+ If `CLOUD_EXPORT_URL` is not set, the package is a no-op — safe to install without configuration.
38
+
39
+ ## Settings
40
+
41
+ | Setting | Default | Description |
42
+ | ------------------------- | ------- | ----------------------------------------------------------- |
43
+ | `CLOUD_EXPORT_URL` | `""` | OTLP ingest endpoint (e.g. `https://ingest.plaincloud.com`) |
44
+ | `CLOUD_EXPORT_TOKEN` | `""` | Auth token for the export endpoint |
45
+ | `CLOUD_TRACE_SAMPLE_RATE` | `1.0` | Probability of exporting a trace (0.0–1.0) |
46
+
47
+ All settings can be set via `PLAIN_`-prefixed environment variables or in `app/settings.py`.
48
+
49
+ ## Sampling
50
+
51
+ By default, all traces are exported. To reduce volume, set a sample rate:
52
+
53
+ ```python
54
+ CLOUD_TRACE_SAMPLE_RATE = 0.1 # Export 10% of traces
55
+ ```
56
+
57
+ Metrics are not affected by sampling — histograms aggregate in-process and export periodically regardless of the trace sample rate.
58
+
59
+ ## What gets exported
60
+
61
+ **Traces** — HTTP request spans and database query spans instrumented by the framework.
62
+
63
+ **Metrics** — OTel histograms like `db.client.query.duration`, aggregated and pushed every 60 seconds.
64
+
65
+ ## Observer coexistence
66
+
67
+ If [plain.observer](../../plain-observer/plain/observer/README.md) is also installed, both work simultaneously. plain.cloud handles production export while observer provides the local dev toolbar and admin trace viewer. Observer detects the existing TracerProvider and layers its sampler and span processor on top.
68
+
69
+ ## FAQs
70
+
71
+ #### Do I need plain.observer to use plain.cloud?
72
+
73
+ No. plain.cloud works independently. Observer is for local dev tooling; plain.cloud is for production export.
74
+
75
+ #### What happens if the export endpoint is unreachable?
76
+
77
+ The OTLP exporters batch and retry automatically. If the endpoint is down, telemetry is dropped after retries — it does not block your application.
78
+
79
+ #### Does this add latency to requests?
80
+
81
+ No. Trace spans are exported in a background thread via `BatchSpanProcessor`. Metrics are flushed periodically by a background thread. Neither blocks request handling.
82
+
83
+ ## Installation
84
+
85
+ ```python
86
+ # app/settings.py
87
+ INSTALLED_PACKAGES = [
88
+ "plain.cloud",
89
+ # ...
90
+ ]
91
+ ```
92
+
93
+ Place `plain.cloud` **before** `plain.observer` in `INSTALLED_PACKAGES` so it sets up the TracerProvider first.
@@ -0,0 +1,9 @@
1
+ plain/cloud/CHANGELOG.md,sha256=KlweN6yIHqtFlAg7IpXjl-g1uUoza9EiYuvaSN-eOvM,615
2
+ plain/cloud/README.md,sha256=_4ZZxysW5qoxKo9Bxvte7Q3u4u296E3cl44DFMq_-ZE,3086
3
+ plain/cloud/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ plain/cloud/config.py,sha256=Cw0Q8toyHem9myPgBLcuYuZsFJdk-CSdrrrk5pD0K1s,2124
5
+ plain/cloud/default_settings.py,sha256=3cAKENWSZZVuc3T33HAbntrgch-0_w6UreI4keDwnGI,260
6
+ plain_cloud-0.1.0.dist-info/METADATA,sha256=9pVfz2fKxHqezJNcpdVa6f-FnOgEzn9XzNW-F6JiGxI,3524
7
+ plain_cloud-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
8
+ plain_cloud-0.1.0.dist-info/licenses/LICENSE,sha256=mgqZwn7Asoev1K7qXKiLuQ6jHBTT8yWRwbbED407z_w,1500
9
+ plain_cloud-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Dropseed, LLC
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.