Skip to main content

Connect your Plain app to Plain Cloud via OTLP export.

Project description

plain.connect

Connect your Plain app to Plain Cloud via OTLP export.

Overview

You can use plain.connect to export traces, metrics, and logs from your Plain app to Plain Cloud. The framework already instruments itself with OpenTelemetry spans and histograms — plain.connect activates them by providing the OTLP exporters and bridges Python's logging module into OTLP log records.

Set one environment variable and your app starts pushing telemetry:

PLAIN_CONNECT_EXPORT_TOKEN=your-token

If CONNECT_EXPORT_TOKEN is not set, the package is a no-op — safe to install without configuration.

Settings

Setting Default Description
CONNECT_EXPORT_URL "https://ingest.plainframework.com" OTLP ingest endpoint (override to use a custom endpoint)
CONNECT_EXPORT_TOKEN "" Auth token for the export endpoint
CONNECT_TRACE_SAMPLE_RATE 1.0 Probability of exporting a trace (0.0–1.0)
CONNECT_EXPORT_LOGS True Set to False to disable OTLP log export
CONNECT_LOG_LEVEL "INFO" Minimum severity exported via OTLP logs (level name or int)
CONNECT_SECRET_KEY "" Shared secret with Plain Cloud (from the App settings page). Encrypts identity tokens, signs render tokens
CONNECT_PAGEVIEWS_TOKEN "" Public pageview-endpoint token; enables the {% connect_pageviews %} tag
CONNECT_PAGEVIEWS_URL "https://beacon.plainframework.com" Pageview ingest endpoint
CONNECT_FORMS_URL "https://plainframework.com/forms" Base URL for support form submissions

All settings can be set via PLAIN_-prefixed environment variables or in app/settings.py.

Sampling

By default, all traces are exported. To reduce volume, set a sample rate:

CONNECT_TRACE_SAMPLE_RATE = 0.1  # Export 10% of traces

Metrics are not affected by sampling — histograms aggregate in-process and export periodically regardless of the trace sample rate.

What gets exported

Traces — HTTP request spans and database query spans instrumented by the framework.

Metrics — OTel histograms like db.client.query.duration, aggregated and pushed every 60 seconds.

Logs — Records from the plain and app loggers, plus anything propagating to the root logger, are bridged into OTLP log records and exported with trace_id / span_id set from the active span. The minimum severity is controlled by CONNECT_LOG_LEVEL (default INFO); the root logger's level is widened to that floor when needed so libraries using getLogger(__name__) reach the exporter. To prevent feedback loops, two sources are skipped on the export path: the opentelemetry namespace, and any record emitted from inside the OTLP exporter's background thread (e.g. urllib3 connection errors raised by the exporter's own HTTP call). Your application's urllib3 logs are exported normally.

Pageview tracking

plain.connect can track page views in your app — anonymous page-view analytics plus, optionally, attribution to your signed-in users. This is independent of the OTLP export above.

Add the tag to your base template, just before </body>:

{% connect_pageviews %}

Then set the public endpoint token:

PLAIN_CONNECT_PAGEVIEWS_TOKEN=plain_pv_...

The tag renders nothing until the token is set. Once enabled, it reports the URL, title, referrer, and a first-party anonymous id on each page load and SPA navigation (History pushState / back-forward).

If your app sends a Content-Security-Policy, add the pageview ingest host to connect-src — the script beacons to CONNECT_PAGEVIEWS_URL (https://beacon.plainframework.com by default). Without it, the browser blocks every pageview beacon.

User attribution

To attribute pageviews to your signed-in users, also set the shared secret:

PLAIN_CONNECT_SECRET_KEY=...

When set, the tag encrypts the signed-in user's id (AES-256-GCM) into an opaque token that only Plain Cloud can decrypt — the raw id never appears in your HTML. The user is read from plain.auth; apps without it (or anonymous visitors) are still counted, their pageviews simply carry no user id.

The same CONNECT_SECRET_KEY is used by the {% connect_support_fields %} widget below — one secret per app.

Support forms

plain.connect ships a tag for sending contact-form submissions to a support endpoint on Plain Cloud. You create the endpoint in the App's Support settings (it gives you an id like plain_sf_abc123), then drop a normal HTML form into your template:

<form action="{{ connect_support_url('plain_sf_abc123') }}" method="POST">
  {% connect_support_fields %}
  <input name="name" placeholder="Your name">
  <input name="email" type="email" placeholder="Email">
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
</form>

The {% connect_support_fields %} tag injects three hidden inputs:

  • plain_connect_render_token — HMAC-signed timestamp; rejected if it's too fresh (bots that submit instantly) or too stale (rendered hours ago)
  • plain_connect_identity — encrypted user id when plain.auth is installed and the visitor is signed in
  • plain_connect_check — honeypot field; submissions that fill it are silently discarded

Both anti-spam signals require CONNECT_SECRET_KEY. Without it the inputs still render (as empty values), so the form remains submittable — you just lose render-token verification and identity attribution.

Field names

A few field names are reserved — the platform reads them directly:

Name Purpose
name Submitter name
email Submitter email
message Body of the submission (required)
form_slug Identifier surfaced in notifications and the inbox (lets one endpoint serve multiple form types)
_next Redirect target after a form-action POST; ignored unless it matches the endpoint's allow-list

Any other field name is captured into the conversation's extras JSON dict automatically — no extra configuration needed.

JSON vs form-action

The endpoint accepts either application/x-www-form-urlencoded (the standard <form action="..."> path) or application/json (for fetch-driven submissions). The field names are the same in both shapes. On the form-action path _next redirects the browser on success; on the JSON path the response is {"ok": true, "conversation": "<uuid>"}.

Observer coexistence

If plain.observer is also installed, both work simultaneously. plain.connect 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.

FAQs

Do I need plain.observer to use plain.connect?

No. plain.connect works independently. Observer is for local dev tooling; plain.connect is for production export.

What happens if the export endpoint is unreachable?

The OTLP exporters batch and retry automatically. If the endpoint is down, telemetry is dropped after retries — it does not block your application.

Does this add latency to requests?

No. Trace spans are exported in a background thread via BatchSpanProcessor. Metrics are flushed periodically by a background thread. Neither blocks request handling.

Installation

# app/settings.py
INSTALLED_PACKAGES = [
    "plain.connect",
    # ...
]

Place plain.connect before plain.observer in INSTALLED_PACKAGES so it sets up the TracerProvider first.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

plain_connect-0.6.0.tar.gz (20.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

plain_connect-0.6.0-py3-none-any.whl (23.7 kB view details)

Uploaded Python 3

File details

Details for the file plain_connect-0.6.0.tar.gz.

File metadata

  • Download URL: plain_connect-0.6.0.tar.gz
  • Upload date:
  • Size: 20.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for plain_connect-0.6.0.tar.gz
Algorithm Hash digest
SHA256 c2f14093f234062e9355c7f7bba14ff1429e5d3a72dfc5e27bb69095ff02447c
MD5 692ea2b93f1f466c292626396a6696a6
BLAKE2b-256 c57a46171fb76231fb84d1849864ee2477f65f2bffff0b05951d074ceb70ecf7

See more details on using hashes here.

File details

Details for the file plain_connect-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: plain_connect-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 23.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for plain_connect-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1b9dd8ad4f44969a28309bf6fa8dffc0d2e4b85a92c35d8373a465a3614e6966
MD5 26ec72d5c864aef53975141291088ab9
BLAKE2b-256 d7c43ccb2b21c4e3a5e9831c2ce6d118266a22775550b3a57494958fb9a7ae35

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page