Send errors
Any Sentry SDK sends to StatOSS with an app's DSN, as do @statoss/sdk, the Python statoss package and the Rust statoss crate.
The DSN
An app is made under Apps in the dashboard. Each of its keys is a DSN, listed under the app's Keys:
https://<key>@ingest.statoss.com/<app number>Any Sentry SDK takes it where Sentry's DSN goes, and sends to /api/<app number>/envelope/ or /store/ as it does for Sentry. Browsers may send from any website, unless the key lists the ones it allows. A revoked key's events are refused. The app's Set up page has these snippets with its DSN filled in.
What is kept, and for how long, is on Errors.
Browser JavaScript: @statoss/sdk
npm install @statoss/sdkimport * as StatOSS from "@statoss/sdk";
StatOSS.init({
dsn: "https://<key>@ingest.statoss.com/<app number>",
environment: "production",
});Node.js: @statoss/sdk
npm install @statoss/sdkconst StatOSS = require("@statoss/sdk");
StatOSS.init({
dsn: "https://<key>@ingest.statoss.com/<app number>",
environment: process.env.NODE_ENV,
});Next.js: @statoss/sdk
npm install @statoss/sdk// instrumentation.ts
import * as StatOSS from "@statoss/sdk";
import type { Instrumentation } from "next";
export function register() {
StatOSS.init({
dsn: "https://<key>@ingest.statoss.com/<app number>",
environment: process.env.NODE_ENV,
});
}
export const onRequestError: Instrumentation.onRequestError = async (error) => {
StatOSS.captureException(error);
await StatOSS.flush(2000);
};
// instrumentation-client.ts
import * as StatOSS from "@statoss/sdk";
StatOSS.init({ dsn: "https://<key>@ingest.statoss.com/<app number>" });React: @statoss/sdk
npm install @statoss/sdkimport * as StatOSS from "@statoss/sdk";
StatOSS.init({
dsn: "https://<key>@ingest.statoss.com/<app number>",
environment: import.meta.env.MODE,
});Python: statoss
Over sentry-sdk; it takes sentry-sdk's options.
pip install statossimport statoss
statoss.init(
dsn="https://<key>@ingest.statoss.com/<app number>",
environment="production",
)Python: the Sentry SDK
pip install sentry-sdkimport sentry_sdk
sentry_sdk.init(
dsn="https://<key>@ingest.statoss.com/<app number>",
environment="production",
)Go: the Sentry SDK
go get github.com/getsentry/sentry-goerr := sentry.Init(sentry.ClientOptions{
Dsn: "https://<key>@ingest.statoss.com/<app number>",
Environment: "production",
})PHP: the Sentry SDK
composer require sentry/sentry\Sentry\init([
'dsn' => 'https://<key>@ingest.statoss.com/<app number>',
'environment' => 'production',
]);Ruby: the Sentry SDK
gem install sentry-rubySentry.init do |config|
config.dsn = "https://<key>@ingest.statoss.com/<app number>"
config.environment = "production"
endRust: statoss
Over the sentry crate; it takes the crate's options.
cargo add statosslet _statoss = statoss::init(
statoss::Options::new()
.dsn("https://<key>@ingest.statoss.com/<app number>")
.release(statoss::release_name!())
.environment("production"),
);Protecting a browser DSN
A DSN in a web page is public, as it is with Sentry: anyone who reads the page can send events with it. Three settings limit what they can do. They are under the app's Settings, Keys and Monthly errors, and in the API and MCP as allowedWebsites, ratePerMinute and errorsShare.
- Allowed websites, per key. Host names such as
example.com, or*.example.comfor the hosts below it (notexample.comitself). A request whoseOrigin, orRefererwhen it has no Origin, names another host gets 403 and counts as filtered,origin_not_allowed. A request with neither header, as a server SDK sends it, is taken: the list keeps other websites from using the key, and does not stop a script that leaves the headers out. Empty allows any website. - Events a minute, per key: 10 to 1,200. Past it the key gets 429, and the app's other keys are not held back.
- Share of the team's monthly errors, per app: 1% to 100%, 50% unless set. Past its share the app gets 429 (
app_quota) until the month turns, and the team's other apps, a server's for one, keep sending. The team's own quota still holds. A team with one app is not held by it: the share only guards the other apps.
Against a script that leaves the headers out, the rate and the share are what hold: a flood stops at the key's rate, and at the app's share of the month instead of the whole team's.
Keeping the DSN off the page
Sentry's browser SDKs take a tunnel option, a URL on your own site that the SDK posts its envelopes to. @statoss/sdk has no such option; its transport option does the same. Your route sends the envelope on to /api/<app number>/envelope/. Intake takes the key from the envelope's first line, its dsn, when the request carries none, so the route sets the real DSN there and the page holds a placeholder with the same app number. A request from your server names no website, so it passes the allowed websites.
// POST /monitoring on your server: sets the real DSN, sends the envelope on.
export async function POST(request: Request) {
const body = await request.text();
const end = body.indexOf("\n");
const header = JSON.parse(body.slice(0, end === -1 ? undefined : end));
header.dsn = process.env.STATOSS_DSN; // https://<key>@ingest.statoss.com/<app number>
const rest = end === -1 ? "" : body.slice(end);
// 42 is the app number.
const res = await fetch("https://ingest.statoss.com/api/42/envelope/", {
method: "POST",
body: JSON.stringify(header) + rest,
});
const headers = new Headers();
for (const name of ["retry-after", "x-sentry-rate-limits"]) {
const value = res.headers.get(name);
if (value) headers.set(name, value);
}
return new Response(null, { status: res.status, headers });
}// In the page: a placeholder key and the app's number.
import * as Sentry from "@sentry/browser";
Sentry.init({
dsn: "https://[email protected]/42",
tunnel: "/monitoring",
});
// With @statoss/sdk, a transport that posts to the same route.
import * as StatOSS from "@statoss/sdk";
StatOSS.init({
dsn: "https://[email protected]/42",
transport: async (request) => {
const res = await fetch("/monitoring", {
method: "POST",
body: request.body,
keepalive: request.keepalive,
});
return { status: res.status, headers: res.headers };
},
});Moving from Sentry
Set the SDK's DSN to the app's; the rest of the code stays as it is. Tags, fingerprints, releases and environments work as they do with Sentry.
sentry-cli uploads source maps, makes and finalizes releases and records deploys against StatOSS with these settings:
export SENTRY_URL=https://statoss.com
export SENTRY_AUTH_TOKEN=<a StatOSS API key that writes>
export SENTRY_ORG=statoss
export SENTRY_PROJECT=<the app's slug>The key names the team; the organization can be any name. The commands are on Errors, Source maps. Not served: projects list, organizations list, releases set-commits, listing a release's deploys, and releases list without a project. Checked with sentry-cli 2.58 and 3.8.
Answers
| Status | Means |
|---|---|
200 | Read. Answers {"id"} with the event's id, or {}. |
400 | Not an envelope, or a body that does not decompress. |
401 | No key. |
403 | Not a live key, not the app's, or a website the key does not allow. |
413 | Over 8 MiB, before or after decompression. |
415 | A Content-Encoding other than gzip, deflate, br or zstd. |
429 | Over the key's events a minute (1,200 at most), the app's share of the month or the team's month; with Retry-After and X-Sentry-Rate-Limits. |
503 | Busy, or starting. |
Errors answer as Sentry's do, so the SDKs back off on a 429 until Retry-After.