<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Sentry Blog</title><description>Product, Engineering, and Marketing updates from the developers of Sentry.</description><link>https://blog.sentry.io/</link><language>en-us</language><item><title>Automated agent triage with Agent Tracing and Claude Routines</title><link>https://blog.sentry.io/claude-routines-agent-triage/</link><guid isPermaLink="true">https://blog.sentry.io/claude-routines-agent-triage/</guid><description>How Sentry uses a Claude Routine and the Sentry MCP to automatically triage 800 AI agent conversations overnight and file bugs.</description><pubDate>Thu, 13 Aug 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every morning, before anyone on the team has looked at a dashboard, a Claude Routine has already read around 800 of the previous night&amp;#39;s conversations from &lt;a href=&quot;https://sentry.io/product/seer/&quot;&gt;Seer, Sentry&amp;#39;s AI agent&lt;/a&gt; for triaging and fixing errors. It flags the ones that look broken, and files tickets for anything new. By the time we sit down with coffee, the triage is mostly done.&lt;/p&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;Before this routine existed, checking on our agent&amp;#39;s behavior meant a custom, one-off flow for pulling data out of our current eval system. Querying options were limited, so most of it came down to hand-rolling a parser for the raw JSON in each trace. Someone had to remember to trigger the analysis and do it manually. Automating this was an option, but we never got around to it due to friction. Plus, we wanted to send the data to the other tools we use day to day, and the current setup didn&amp;#39;t have that kind of connectivity.&lt;/p&gt;
&lt;p&gt;We were looking for a mix of things. Tool call failures, hallucinated outputs, latency spikes, agent loops that went on longer than they should have, and cost anomalies. All of the above, really.&lt;/p&gt;
&lt;p&gt;We had three main goals. Improve reliability by cutting down tool errors, keep an eye on the long tail of unusual runs (the ones that were unusually expensive or made far more tool calls than a typical conversation), and sample the conversations that looked good to actually confirm they were good, not just assume it.&lt;/p&gt;
&lt;h2&gt;The Claude Routine&lt;/h2&gt;
&lt;p&gt;The routine runs each morning as a &lt;a href=&quot;https://code.claude.com/docs/en/routines&quot;&gt;Claude Routine&lt;/a&gt;. It connects through the &lt;a href=&quot;https://mcp.sentry.dev/&quot;&gt;Sentry MCP&lt;/a&gt; (a Model Context Protocol server that lets Claude query Sentry directly) to run &lt;a href=&quot;https://sentry.io/product/tracing/ai-agent/&quot;&gt;Agent Tracing&lt;/a&gt; queries, plus a handful of general queries, against the previous night&amp;#39;s conversations. Each run works through the same steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Pull aggregate stats for the period i.e., how many conversations ran overnight.&lt;/li&gt;
&lt;li&gt;Pull aggregate tool stats: i.e., how many tools had errors, and at what rate.&lt;/li&gt;
&lt;li&gt;From that list of errored tools, sample some spans to see real examples and trends behind the failures.&lt;/li&gt;
&lt;li&gt;Sample a mix of full conversations, both with and without tool errors, and judge whether the agent&amp;#39;s final verdict actually matches its reasoning.&lt;/li&gt;
&lt;li&gt;Pull in the Seer codebase itself for more context.&lt;/li&gt;
&lt;li&gt;Roll all of that into a single report of new findings.&lt;/li&gt;
&lt;li&gt;Search our Linear project for existing tickets, and file new ones only for what isn&amp;#39;t already tracked.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The prompt behind it is intentionally plain to let the LLM handle most of the work and judgment. The routine queries Sentry directly through the Sentry MCP, so there&amp;#39;s no extra tooling to set up. That&amp;#39;s all it takes.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Use the Sentry MCP to look at the last 24hr of &amp;lt;product&amp;gt; conversations
and analyze the results. In particular look for errors that might be
happening during the &amp;lt;product_stage&amp;gt;, say if our tools are broken. But
also sample some success conversations as well to see if they look
correct with secondary inspection.

If you come across new findings/errors, record them in a Linear ticket
for the &amp;lt;linear_project&amp;gt; project.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Sentry MCP handles pulling and filtering conversations. Claude handles the parts that need judgment: reading a conversation, sampling for trends, and deciding whether something is actually worth a ticket.&lt;/p&gt;
&lt;h2&gt;What this looks like in practice&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Analyzed the last 24h of &amp;lt;feature&amp;gt; conversations (~445-551 total,
~11k tool calls). No verdict-quality problems found — spot-checked
several conversations and the agent&amp;#39;s reasoning was well-grounded,
correctly downgrading when it lacked repo access rather than
fabricating.

Did find an efficiency issue worth tracking: ~21% of conversations
(83/400 sampled) hit at least one tool error, mostly self-corrected
retries. Root causes:

Search Code sometimes gets fed malformed repo_name values (e.g.
appending a monorepo subpath onto the real slug, like
&amp;quot;foo/bar/applications/integrations&amp;quot; instead of &amp;quot;foo/bar&amp;quot;), causing
&amp;quot;repository not found&amp;quot; until it retries.

 sometimes gets a guessed-wrong project_slug (e.g. &amp;quot;mobile&amp;quot;)
before self-correcting on retry with the right slug + time window.

Two conversations showed 30-56s hangs before an opaque &amp;quot;internal
error&amp;quot; — a real backend latency signal, distinct from the guessing
pattern above, worth an engineering look.

Filed Linear ticket AIML-123 with full details, example conversation
links, and error-rate tables: https://linear.app/getsentry/issue/AIML-123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The repo-hallucination case above is a good example of what a typical flagged morning looks like. The routine&amp;#39;s tool-error stats surfaced Search Code as having an outsized error rate for the period. Sampling a few of the underlying spans showed the same shape every time: the agent guessing at repo names, one after another, none of them landing. We pulled up the full conversations and it was the same thing every time. The agent had no idea which repo it was looking for.&lt;/p&gt;
&lt;p&gt;Aggregate error rates alone wouldn&amp;#39;t show this. Neither would reading one conversation at a time. It took both. And once we saw it, the fix was straightforward. Just give the agent the repo name instead of making it guess.&lt;/p&gt;
&lt;h2&gt;Lessons learned&lt;/h2&gt;
&lt;p&gt;Running this daily made the feedback loop fast enough that most fixes landed the same day we spotted them.&lt;/p&gt;
&lt;p&gt;As for some best practices:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Sampling both success and failure conversations, not just the ones with tool errors, mattered more than expected. A conversation can complete without a single tool error and still reach the wrong conclusion — that only shows up when something (or someone) checks the reasoning against the verdict.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keeping the prompt focused on judgment rather than data retrieval made the output far more consistent morning to morning. The Sentry MCP handles the querying, so the routine doesn&amp;#39;t need to reconstruct trace-querying logic from scratch every run.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;How to do this yourself&lt;/h2&gt;
&lt;p&gt;With Claude Routines this is very simple. First, make sure &lt;a href=&quot;https://docs.sentry.io/product/agents/&quot;&gt;Agent Tracing&lt;/a&gt; is set up for your agent. Then, add a Claude Routine with a prompt similar to mine that tells it to use the Sentry MCP to find agent traces for your feature. Lastly, make sure the Sentry MCP Connector (and others like Linear) is configured and enabled for the routine so that it can correctly authenticate to Sentry. That&amp;#39;s it! For a full walkthrough, see the &lt;a href=&quot;https://sentry.io/cookbook/automate-ai-agent-triage-claude-routines/&quot;&gt;step-by-step recipe&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>What&apos;s new in Sentry Logs: The summer 2026 roundup</title><link>https://blog.sentry.io/sentry-logs-summer-2026-roundup/</link><guid isPermaLink="true">https://blog.sentry.io/sentry-logs-summer-2026-roundup/</guid><description>Everything that shipped for Sentry Logs this summer: log pinning, JSONL exports, terabyte-scale search, and a dozen usability improvements.</description><pubDate>Tue, 11 Aug 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We got a little behind on updating our changeLOG, so we&amp;#39;re dumping it all into this bLOG post instead. Think of it as one giant, retroactive changelog entry or, if you want to be dramatic about it, one massive prompt injection straight into your feed. Either way: here&amp;#39;s everything that shipped for &lt;a href=&quot;https://sentry.io/product/logs/&quot;&gt;Sentry Logs&lt;/a&gt; this summer.&lt;/p&gt;
&lt;p&gt;Would you rather listen to the team talk about what they built? Check out &lt;a href=&quot;https://www.youtube.com/watch?v=3dirIYmeGYg&quot;&gt;this video where Kyle and Josh talk about the latest updates on Logs&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Pin the log line that matters&lt;/h2&gt;
&lt;p&gt;Sentry now has &lt;a href=&quot;https://sentry.io/changelog/log-pinning/&quot;&gt;log pinning&lt;/a&gt;, so the next time you spot something interesting, you can pin the log line so it stays put while you keep scrolling, streaming in new logs, or refining your search. Pins are also synced to URL state, so a pinned view is a real, shareable link for the next time you want to show a teammate exactly what you&amp;#39;re looking at.&lt;/p&gt;
&lt;h2&gt;Export logs at scale&lt;/h2&gt;
&lt;p&gt;We overhauled logs exports to handle large volumes faster and let you customize exactly what you&amp;#39;re exporting.&lt;/p&gt;
&lt;p&gt;Now, exports go through a &lt;a href=&quot;https://sentry.io/changelog/export-logs-as-jsonl-or-csv/&quot;&gt;dedicated export dialog&lt;/a&gt; where you choose your row count and format up front, including a new JSONL option built for larger exports. The result is a faster export and considerably lighter on our servers, because we&amp;#39;re no longer guessing at what &amp;quot;export everything&amp;quot; should mean.&lt;/p&gt;
&lt;p&gt;And because exports of individual log rows now include full attribute data, not just what&amp;#39;s visible in the table, they&amp;#39;re more useful if you want to pull a batch of logs into your own tooling.&lt;/p&gt;
&lt;p&gt;Exporting is also now aggregate-aware: if you export while viewing an aggregate (say, a count grouped by some attribute), you get those aggregates, matching what you were actually looking at, instead of always falling back to the raw underlying logs.&lt;/p&gt;
&lt;p&gt;This isn&amp;#39;t a logs-only improvement, either. The same underlying export capability is now available across Errors and Tracing.&lt;/p&gt;
&lt;h2&gt;Usability improvements for the log viewer&lt;/h2&gt;
&lt;p&gt;The logs page is faster now. A new, more constrained layout plus loading less data up front both add up to quicker load times. Beyond that, the rest of this is basically our bug tracker&amp;#39;s greatest hits to make using Sentry Logs more delightful to use:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Filters in the trace view Logs tab.&lt;/strong&gt; Previously, the Logs tab inside a trace had no way to filter at all. Now you can narrow down what you&amp;#39;re looking at without leaving the trace.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Every matching search term gets highlighted&lt;/strong&gt;, not just the first one that matches in a line.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The error instance now shows up inline&lt;/strong&gt; in the logs table on an issue, so you can see the error and the logs around it together instead of cross-referencing two views.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Timestamp is now always visible&lt;/strong&gt; in the log details panel, even if you arrived from a dashboard link where the timestamp column had been dropped from the table.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Multi-value search filters now show as individual chips&lt;/strong&gt;, instead of one long comma-separated value. Previously, &lt;code&gt;message contains abc,def&lt;/code&gt; rendered as a single confusing text blob. Now &lt;code&gt;abc&lt;/code&gt; and &lt;code&gt;def&lt;/code&gt; are separate chips.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Drag-to-zoom no longer wrecks your browser history.&lt;/strong&gt; It used to write three history entries per zoom, so hitting &amp;quot;back&amp;quot; didn&amp;#39;t take you where you expected. Now it&amp;#39;s one zoom, one entry.&lt;/p&gt;
&lt;h2&gt;Logs chart enhancements&lt;/h2&gt;
&lt;p&gt;We also made some UX enhancements to our logs charts. In case you haven&amp;#39;t noticed, a lot of it has to do with giving you more room on the screen for your actual logs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Charts can now expand and contract, with a standardized button in the top-right corner. Expand a chart when you want more room to look at it; contract it back down when you&amp;#39;d rather have the space for your logs.&lt;/li&gt;
&lt;li&gt;Y-axis ticks now scale with the chart&amp;#39;s actual height, not your browser&amp;#39;s viewport height. Resize your window and the chart stays legible instead of collapsing down to two ticks.&lt;/li&gt;
&lt;li&gt;Chart hover tooltips are a lot less flaky and no longer immediately disappear if you have auto-refresh enabled.&lt;/li&gt;
&lt;li&gt;We expanded the color palette for grouped series, so it&amp;#39;s easier to tell apart what you&amp;#39;re looking at when a query returns more than a couple of groups.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Scaling logs data with Sentry&lt;/h2&gt;
&lt;p&gt;For orgs sending hundreds of gigabytes or terabytes of logs, the hard problem was never storing the data, it was finding one specific line out of all of it: a needle-in-the-haystack search.&lt;/p&gt;
&lt;p&gt;We put in frontend and backend work this quarter specifically to support that scale, including longer query timeouts with continuations and better handling around rate limits, so search stays usable even with a genuinely large volume of logs.&lt;/p&gt;
&lt;p&gt;A couple of smaller fixes ride along with this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;release:latest&lt;/code&gt; filters now work correctly in Logs and Trace Explorer. They previously returned empty results in some flows, like saved dashboard widgets, which made it look like there was nothing to see when there was.&lt;/li&gt;
&lt;li&gt;Rate limit errors say what they are. If you hit a throughput limit, the UI tells you that you&amp;#39;re being rate limited, instead of surfacing a generic error that looks like something&amp;#39;s broken. And there&amp;#39;s a retry button now, so you don&amp;#39;t have to refresh the entire page to try again.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Try it and tell us what you want to see next&lt;/h2&gt;
&lt;p&gt;We know a list of shipped features isn&amp;#39;t exactly thrilling reading and if you made it to the end of our updates, thank you.&lt;/p&gt;
&lt;p&gt;Don&amp;#39;t forget, if something is broken, missing, or just annoying, we want to hear about it. Hit the product feedback button in-app, or reach our team directly at &lt;a href=&quot;mailto:logs@sentry.io&quot;&gt;logs@sentry.io&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>How we built an automated debugging workflow at Sentry</title><link>https://blog.sentry.io/automated-debugging-workflow-sentry/</link><guid isPermaLink="true">https://blog.sentry.io/automated-debugging-workflow-sentry/</guid><description>How Sentry uses Seer autofix and Claude routines to build an automated debugging workflow that detects, fixes, and routes code issues automatically.</description><pubDate>Thu, 06 Aug 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;AI is going to generate a lot of code from here on out, and a lot of bugs along with it. You already know this. We&amp;#39;ve talked about it before. The bigger challenge is making sure you don&amp;#39;t spend all your time fixing the broken code your agents write.&lt;/p&gt;
&lt;p&gt;You&amp;#39;re going to need a system that makes it easier to fix those issues for you and fortunately, there are a lot of solutions out there for building automated workflows.&lt;/p&gt;
&lt;p&gt;Recently, we&amp;#39;ve been exploring ways to automate our debugging and issue triage process here at Sentry. We also thought it might be helpful to pass along some of our tips and learnings in case you want to try it yourself.&lt;/p&gt;
&lt;h2&gt;It all starts with knowing what&amp;#39;s broken and why&lt;/h2&gt;
&lt;p&gt;If you&amp;#39;re going to have AI resolve issues for you, you first need to know when issues come up. No surprise: we use &lt;a href=&quot;https://docs.sentry.io/&quot;&gt;Sentry&lt;/a&gt; a lot here at Sentry. We&amp;#39;ve instrumented our application to emit all kinds of signals and &lt;a href=&quot;https://docs.sentry.io/product/metrics/&quot;&gt;metrics&lt;/a&gt;, which gives us detailed insight into how things are running.&lt;/p&gt;
&lt;p&gt;As you can imagine, that means our &lt;a href=&quot;https://docs.sentry.io/product/issues/&quot;&gt;Issues page&lt;/a&gt; can get quite full, which creates a lot of manual work in &lt;a href=&quot;https://docs.sentry.io/product/issues/states-triage/&quot;&gt;triage&lt;/a&gt;. That&amp;#39;s where we bring in &lt;a href=&quot;https://docs.sentry.io/product/ai-in-sentry/seer/&quot;&gt;Seer&lt;/a&gt;, our AI-powered debugging agent. We have Seer&amp;#39;s &lt;a href=&quot;https://docs.sentry.io/product/ai-in-sentry/seer/autofix/&quot;&gt;autofix&lt;/a&gt; set up across a number of Sentry projects, like core Sentry, our documentation, MCP server, and CLI tool, and their corresponding &lt;a href=&quot;https://docs.sentry.io/integrations/source-code-mgmt/github/&quot;&gt;GitHub repositories&lt;/a&gt;. We&amp;#39;ve also given Seer permission to find the &lt;a href=&quot;https://docs.sentry.io/product/ai-in-sentry/seer/autofix/#root-cause-analysis&quot;&gt;root cause&lt;/a&gt; of an issue, write a plan to fix it, and &lt;a href=&quot;https://docs.sentry.io/product/ai-in-sentry/seer/autofix/#code-generation&quot;&gt;open pull requests&lt;/a&gt; for us automatically. Your org might not be ready to trust the machines that far, and that&amp;#39;s ok. You can control how far Seer goes before a human joins the process. Automation is all about creating less manual work for your engineers, not reducing the number you have.&lt;/p&gt;
&lt;p&gt;Automatic PR generation is useful, but it raises another question: how do you know when there&amp;#39;s a PR to review, and who should review it?&lt;/p&gt;
&lt;h2&gt;Creating a routine for review&lt;/h2&gt;
&lt;p&gt;The hard part of automation usually isn&amp;#39;t the difficulty of the tasks, it&amp;#39;s managing those tasks as they grow in scale. It&amp;#39;s useful that agents can handle small work like writing a code fix, but their real value is the efficiency they create across a system. In our case, Seer is now generating a lot of new pull requests, so how do we make sure they get actioned without inventing a new role like Chief Delegation Officer? More agents.&lt;/p&gt;
&lt;p&gt;When we kicked off this project, we had two focus areas:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Automate the process of creating a fix&lt;/li&gt;
&lt;li&gt;Bring in an engineer when there&amp;#39;s something for them to pick up&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We have a &lt;a href=&quot;https://docs.sentry.io/integrations/notification-incidents/slack/&quot;&gt;Slack channel&lt;/a&gt; set up that notifies us whenever Seer opens a new pull request. This uses a new &lt;a href=&quot;https://docs.sentry.io/product/monitors-and-alerts/alerts/#set-triggers&quot;&gt;alert trigger we recently added&lt;/a&gt;. It&amp;#39;s a general feed — about 90% of the posts are new-PR notifications, with the occasional conversation mixed in. Instead of going through each one by hand, we use a Claude routine. Here&amp;#39;s how we built ours.&lt;/p&gt;
&lt;h3&gt;Runs on a schedule&lt;/h3&gt;
&lt;p&gt;The routine runs hourly and checks our Slack feed for new PRs, we classify that as any PRs that were opened and not actioned upon in the last four hours. That keeps us working only from fresh PRs.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## PROMPT

You automate the Seer fix-reviewer verification flow in the &amp;lt;#sentry-slack-channel&amp;gt; 
Slack channel (channel_id ). 

This runs every 4 hours, 7 days a week, and unattended — execute autonomously, 
make reasonable choices, and never ask questions. 

Your Slack user_id is . The Sentry bot is . 
End with a short per-PR report.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Identifies a reviewer&lt;/h3&gt;
&lt;p&gt;First, Claude sends a pre-written Slack message to the &lt;a href=&quot;https://sentry.io/product/seer/agent/&quot;&gt;Seer agent&lt;/a&gt; with a prompt asking who&amp;#39;s best suited to review the PR. Seer looks at commit history in our GitHub repository and finds the person who has worked most closely with the code flagged in Seer&amp;#39;s &lt;a href=&quot;https://docs.sentry.io/product/ai-in-sentry/seer/autofix/&quot;&gt;root cause analysis&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Checks that the PR is open&lt;/h3&gt;
&lt;p&gt;Before assigning a PR, we make sure it still exists. We check its status, and if it&amp;#39;s already merged, closed, or otherwise off, we skip it silently.&lt;/p&gt;
&lt;h3&gt;Pings the reviewer&lt;/h3&gt;
&lt;p&gt;If the PR is valid, the routine tags the reviewer in the thread with the Seer-created PR and asks them to wrap it up by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Merging or closing the PR&lt;/li&gt;
&lt;li&gt;Leaving one line of feedback&lt;/li&gt;
&lt;li&gt;Noting whether this was a good use of Seer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We also built the routine so it never double-posts a prompt, question, or DM. The goal is for the agent to streamline our work, not add noise to it.&lt;/p&gt;
&lt;p&gt;To be clear, this wasn&amp;#39;t a one-shot setup. We spent a couple of weeks iterating on it. For example, we needed to make sure that engineers weren&amp;#39;t getting pinged on the weekend or that they weren&amp;#39;t getting pinged right after assignment. These were things we only learned by trial and error, and we&amp;#39;re still looking for ways to improve the overall flow. There isn&amp;#39;t a one-size-fits-all-orgs solution here; the key is standing something up that feels right and then iterating out the wrinkles.&lt;/p&gt;
&lt;h2&gt;Early results&lt;/h2&gt;
&lt;p&gt;One caveat: we only rolled this workflow out in mid-July, so we&amp;#39;ll need to revisit it in a few months to really judge how it&amp;#39;s working. Early on, though, the signs are positive. A few of the numbers we&amp;#39;re seeing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;About a 21% increase in the action rate on PRs&lt;/li&gt;
&lt;li&gt;About a 13% increase in the 48-hour response rate on open PRs&lt;/li&gt;
&lt;li&gt;About a 12.5% increase in close-without-merge PRs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Looking at these results, you might think an increase in close-without-merge isn&amp;#39;t a great stat, so why share it? It could mean the proposed fixes aren&amp;#39;t useful, or that they don&amp;#39;t solve the root of the problem. So we did a little more digging. That wasn&amp;#39;t the case.&lt;/p&gt;
&lt;p&gt;The close-without-merge PRs followed a couple of common themes. They were either a duplicate fix already covered in a separate PR, or the reviewing engineer opted for a more comprehensive fix that went beyond the scope of the original issue. In both cases, the workflow is doing its job. Engineers are getting notified that fixes are ready and taking action on those PRs. That beats a pile of untouched PRs sitting there.&lt;/p&gt;
&lt;p&gt;If there&amp;#39;s one takeaway here, it&amp;#39;s this: automated debugging is possible, and it&amp;#39;s genuinely useful. The key to that is building a system that brings in the right people at the right time.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.sentry.io/product/ai-in-sentry/seer/&quot;&gt;Seer&lt;/a&gt; is the engine that powers this whole workflow for us. We rely on Seer to help us:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Diagnose issues&lt;/li&gt;
&lt;li&gt;Propose a fix&lt;/li&gt;
&lt;li&gt;Alert our Slack channel when it creates a PR&lt;/li&gt;
&lt;li&gt;Identify and inform the right engineer to keep in the loop&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We add a bit of automation with Claude, but largely are leveraging Seer for as much as we can. It&amp;#39;s also inspired us to build even more functionality into Seer to enable this loop. We&amp;#39;re exploring ways to have Seer assign PRs to the most relevant user and notify them automatically.&lt;/p&gt;
&lt;h2&gt;Try it yourself&lt;/h2&gt;
&lt;p&gt;Best of all, everything we&amp;#39;ve described is available for you to try today, so now it&amp;#39;s your turn. Here&amp;#39;s the setup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.sentry.io/integrations/notification-incidents/slack/#seer-agent&quot;&gt;Seer agent in Slack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sentry.io/product/seer/autofix/&quot;&gt;Seer autofix&lt;/a&gt; enabled on a project&lt;/li&gt;
&lt;li&gt;Claude routines or Cursor automation&lt;/li&gt;
&lt;li&gt;GitHub CLI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One quick note about Seer autofix. It will not create a PR for every issue automatically. Before creating a PR, Seer assesses the fixability and severity of the issue, details of that criteria can be found in our &lt;a href=&quot;https://docs.sentry.io/product/ai-in-sentry/seer/autofix/#when-autofix-runs-automatically&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;New to Seer autofix? Try it &lt;a href=&quot;https://sentry.io/orgredirect/settings/seer/trial&quot;&gt;free for 14 days&lt;/a&gt;. You won&amp;#39;t be charged when the trial ends — you&amp;#39;ll only pay if you decide to keep it. After that, it&amp;#39;s $40 per &lt;a href=&quot;https://docs.sentry.io/pricing/quotas/manage-seer-budget/#key-terms&quot;&gt;active contributor&lt;/a&gt;, per month, for unlimited use.&lt;/p&gt;
</content:encoded></item><item><title>Your OTel spans, our errors: A Sentry love story in one trace</title><link>https://blog.sentry.io/otel-spans-errors-sentry-trace/</link><guid isPermaLink="true">https://blog.sentry.io/otel-spans-errors-sentry-trace/</guid><description>The OtlpIntegration bridges OTel traces and Sentry errors. Keep your OTel setup, add Sentry for errors, and see both in one trace waterfall.</description><pubDate>Wed, 05 Aug 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You can already send OTel traces to Sentry. Point your OTLP exporter at Sentry&amp;#39;s endpoint, set environment variables, and your spans show up in the trace explorer. Our &lt;a href=&quot;https://blog.sentry.io/send-your-existing-opentelemetry-traces/&quot;&gt;OTLP setup guide&lt;/a&gt; and &lt;a href=&quot;https://blog.sentry.io/sentry-opentelemetry-work-together/&quot;&gt;&amp;quot;You Don&amp;#39;t Need to Pick One&amp;quot;&lt;/a&gt; walk you through that.&lt;/p&gt;
&lt;p&gt;But those spans are islands. You get a trace waterfall in Sentry, sure. What you don&amp;#39;t get is the thing that makes Sentry valuable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click an error =&amp;gt; see the OTel trace that caused it&lt;/li&gt;
&lt;li&gt;Click a span =&amp;gt; see the Sentry errors that fired inside&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That connection between your OTel performance data and your Sentry error data doesn&amp;#39;t exist unless something on the SDK side actively wires them together.&lt;/p&gt;
&lt;p&gt;That&amp;#39;s what the &lt;code&gt;OtlpIntegration&lt;/code&gt; for Python, Ruby, Node.js, Go, PHP, .NET, and Java does.&lt;/p&gt;
&lt;h2&gt;Trace connectedness: the problem the raw OTLP endpoint can&amp;#39;t solve&lt;/h2&gt;
&lt;p&gt;When you use a standalone OTel SDK with Sentry&amp;#39;s OTLP endpoint, two separate systems run in your process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The OTel part manages traces and spans.&lt;/li&gt;
&lt;li&gt;The Sentry SDK manages errors, logs, application metrics, session replay, and profiling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They share a process but not a context. When the Sentry SDK captures an error, it doesn&amp;#39;t know which OTel trace is active. When the OTel SDK exports a span, it doesn&amp;#39;t know a Sentry error happened during that span.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;OtlpIntegration&lt;/code&gt; bridges that gap. It reads the active OTel trace context and stamps the &lt;code&gt;trace_id&lt;/code&gt; and &lt;code&gt;span_id&lt;/code&gt; onto every Sentry event. That&amp;#39;s what makes an error show up inside its OTel trace waterfall, and what makes the trace waterfall show which spans had errors.&lt;/p&gt;
&lt;h2&gt;What the OtlpIntegration does under the hood&lt;/h2&gt;
&lt;p&gt;It does three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Configures an OTLP exporter&lt;/strong&gt; pointed at Sentry&amp;#39;s ingestion endpoint, derived from your DSN. Your OTel spans flow into Sentry over standard OTLP.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reads the active OTel trace context&lt;/strong&gt; and stamps it onto every Sentry event (errors, logs, application metrics, etc.) so they appear connected in the trace waterfall.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optionally registers a SentryPropagator&lt;/strong&gt; for compatibility with services using Sentry&amp;#39;s native tracing. (Note: this auto-setup is being removed in the next major version across all SDKs, and propagation will be a documented opt-in instead.)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Why this replaced our previous approach&lt;/h2&gt;
&lt;p&gt;Our previous strategy, which we call POTel, deeply integrated the Sentry SDK with OpenTelemetry. That approach had its benefits: Sentry and OpenTelemetry spans could be interleaved within the same trace, context propagation was unified, and users could take advantage of OpenTelemetry&amp;#39;s instrumentation ecosystem with minimal friction.&lt;/p&gt;
&lt;p&gt;Our new approach makes a different trade-off. Rather than requiring every Sentry SDK to integrate deeply with OpenTelemetry, backend SDKs now work independently, with no mandatory OpenTelemetry dependency or coupling to OpenTelemetry&amp;#39;s release cycle. In exchange, we give up in-process span interleaving, but gain a solution that is consistent, portable, and practical across all supported backend SDKs.&lt;/p&gt;
&lt;p&gt;For users who need the tighter integration, Java continues to support the original POTel approach, where its mature OpenTelemetry ecosystem makes that model a good fit.&lt;/p&gt;
&lt;h2&gt;Two env vars and an init call&lt;/h2&gt;
&lt;p&gt;For Python, all you need is two environment variables for the exporter and two lines in your initialization code.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
from sentry_sdk.integrations.otlp import OtlpIntegration

sentry_sdk.init(
    dsn=&amp;quot;https://&amp;lt;key&amp;gt;@&amp;lt;org&amp;gt;.ingest.us.sentry.io/&amp;lt;project&amp;gt;&amp;quot;,
    integrations=[OtlpIntegration()],
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The integration picks up the OTLP exporter config from &lt;code&gt;OTEL_EXPORTER_OTLP_TRACES_ENDPOINT&lt;/code&gt; and &lt;code&gt;OTEL_EXPORTER_OTLP_TRACES_HEADERS&lt;/code&gt;, sets up a &lt;code&gt;TracerProvider&lt;/code&gt; with a &lt;code&gt;BatchSpanProcessor&lt;/code&gt;, and wires trace context onto Sentry events. If you already have your own &lt;code&gt;TracerProvider&lt;/code&gt;, the integration detects it and skips auto-setup. Other SDKs follow the same pattern with language-appropriate idioms. &lt;a href=&quot;https://docs.sentry.io/concepts/otlp/sentry-with-otel/&quot;&gt;See the OTLP integration docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And here&amp;#39;s what you get in Sentry:&lt;/p&gt;
&lt;h2&gt;Where POTel still lives (and where it doesn&amp;#39;t)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Java&lt;/strong&gt; is the only SDK where POTel is the current long-term path. Java&amp;#39;s OTel ecosystem is mature and stable. The deep integration gives real value there: Sentry scopes propagation, automatic breadcrumb capture, the works. The OTLP integration exists as an additional option, not a replacement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JavaScript (Node.js)&lt;/strong&gt; is in transition. Today, the full Node.js SDK still bundles OpenTelemetry with automatic instrumentation (POTel). We&amp;#39;ve also shipped a lightweight mode (imported from &lt;code&gt;@sentry/node-core/light&lt;/code&gt;) that drops the OpenTelemetry dependencies entirely. You still get errors, logs, metrics, and manual spans; you just don&amp;#39;t get automatic instrumentation. In v11, we take a similar lightweight approach. There is no OTel &lt;code&gt;TracerProvider&lt;/code&gt; set up, but you do get automatic, Sentry-native auto-instrumentation.&lt;/p&gt;
&lt;h2&gt;Who is this for?&lt;/h2&gt;
&lt;p&gt;This is a great fit for teams that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Are already invested in OTel for backend tracing.&lt;/strong&gt; They have &lt;code&gt;TracerProvider&lt;/code&gt; set up, auto-instrumentation running, maybe even an OTel Collector. They don&amp;#39;t want to rip that out to adopt Sentry tracing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use (or evaluate) Sentry for errors.&lt;/strong&gt; The value proposition: &amp;quot;keep your OTel traces, add Sentry for errors, and the integration connects them. Click an error, see the trace. Click a span, see the errors.&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run a polyglot backend.&lt;/strong&gt; The integration works across multiple Sentry SDKs. An org with Python microservices talking to Go services talking to a .NET monolith can instrument each with OTel and see unified traces in Sentry.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Traces and logs yes, errors and metrics no&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;You get:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OTel spans in Sentry&amp;#39;s trace waterfall, stitched with Sentry errors, logs, and other events on the same trace&lt;/li&gt;
&lt;li&gt;Distributed tracing across services, whether they use Sentry native tracing or OTel&lt;/li&gt;
&lt;li&gt;OTLP log ingestion (but see caveat below)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;You don&amp;#39;t get:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Errors via OTLP.&lt;/strong&gt; The most common misconception. Errors still require a Sentry SDK. OTLP handles traces and logs, not exceptions. If a customer only wants to point an OTLP exporter at Sentry and skip the SDK, they get traces but no error tracking.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interleaved Sentry and OTel spans in the same process.&lt;/strong&gt; You choose one tracing system per service. Don&amp;#39;t set &lt;code&gt;traces_sample_rate&lt;/code&gt; on the Sentry SDK if you&amp;#39;re using OTel tracing, because it produces duplicate spans.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OTLP logs connected to errors without OTLP traces.&lt;/strong&gt; The log-to-error link depends on shared trace context. If you only need logs without tracing, Sentry&amp;#39;s native logging is the better path.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Propagation is becoming opt-in&lt;/h2&gt;
&lt;p&gt;The automatic &lt;code&gt;SentryPropagator&lt;/code&gt; setup is being removed across all SDKs (Python, Ruby, .NET, Java). OTel only allows one global propagator at a time, so silently registering one can conflict with whatever the user has already configured. Users who need &lt;code&gt;sentry-trace&lt;/code&gt; header injection for mixed environments will set up a composite propagator themselves.&lt;/p&gt;
&lt;p&gt;Check out &lt;a href=&quot;https://sentry.io/solutions/opentelemetry/&quot;&gt;Sentry for OpenTelemetry&lt;/a&gt; or the &lt;a href=&quot;https://docs.sentry.io/concepts/otlp/sentry-with-otel/&quot;&gt;OTLP integration docs&lt;/a&gt; for setup details.&lt;/p&gt;
&lt;p&gt;And that&amp;#39;s pretty much all there is to know. Keep your OTel, add Sentry, and let one trace tell the whole story.&lt;/p&gt;
</content:encoded></item><item><title>Session Replay for Unreal Engine: see the crash before the crash</title><link>https://blog.sentry.io/session-replay-unreal-engine/</link><guid isPermaLink="true">https://blog.sentry.io/session-replay-unreal-engine/</guid><description>The Sentry Unreal Engine SDK now records the last seconds of gameplay and attaches the footage to crash reports, so you can watch what led to the crash.</description><pubDate>Fri, 24 Jul 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You know the drill: a crash report lands in your queue with a pristine stack trace pointing at some innocent-looking function and absolutely no clue about what the player was actually doing. Were they mid-boss-fight? Alt-tabbing during a loading screen? Standing perfectly still in the tutorial? QA can&amp;#39;t repro it, the player&amp;#39;s bug report says &amp;quot;game crashed lol,&amp;quot; and you&amp;#39;re left staring at a callstack playing twenty questions with a core dump.&lt;/p&gt;
&lt;p&gt;Stack traces tell you &lt;strong&gt;where&lt;/strong&gt; the code died but not always &lt;strong&gt;why&lt;/strong&gt;. Since games tend to be much less deterministic than typical desktop or mobile apps, the &amp;quot;why&amp;quot; often lives in the seconds before the crash.&lt;/p&gt;
&lt;p&gt;That&amp;#39;s exactly the gap &lt;a href=&quot;https://docs.sentry.io/product/session-replay/&quot;&gt;Session Replay&lt;/a&gt; fills. With its new Session Replay support, the Sentry SDK for Unreal Engine can now continuously record the last several seconds of gameplay and attach that clip to the crash report. When the crash lands in Sentry, so does the footage, allowing you to watch what the player saw, right up to the moment things went sideways.&lt;/p&gt;
&lt;h2&gt;What you get with Session Replay&lt;/h2&gt;
&lt;p&gt;When a crash with a replay arrives, it shows up on the &lt;a href=&quot;https://sentry.io/orgredirect/organizations/:orgslug/replays/&quot;&gt;Replays&lt;/a&gt; page linked to the crash event as a playback with the actual rendered frames of your game and a scrubbable timeline of everything important that was going on along the way.&lt;/p&gt;
&lt;p&gt;The panels next to the player are where the debugging happens. Alongside the footage and an AI replay summary, each replay gives you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tags &amp;amp; context&lt;/strong&gt; - the answers you&amp;#39;d normally have to ask the player for: OS and version, release, crash type, region, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Breadcrumbs&lt;/strong&gt; - errors, gameplay events, and your own custom breadcrumbs on a timeline synced with the video.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logs&lt;/strong&gt; - the game&amp;#39;s log output timestamped against the video.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trace&lt;/strong&gt; - the spans leading up to the crash, linked by a shared trace ID. If the crash involves your backend, the server-side spans of the same trace are accessible from here as well.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Looking at the example above, those things tell the full story: three seconds in, an ensure fires because a checkout HTTP request came back invalid; the player reaches level 2, picks the &amp;quot;Wrath&amp;quot; attack modifier, and at 00:22 - fatal crash in &lt;code&gt;ASentryTowerTurret::Shoot&lt;/code&gt;. That&amp;#39;s a reproduction script, handed to you by the crash itself.&lt;/p&gt;
&lt;p&gt;In fact, you don&amp;#39;t even have to leave the crash to start watching. The replay is embedded right on the crash event page, so triaging an issue can start with a quick glance at the last few seconds of gameplay. If that glance raises questions, the full replay page is one click away.&lt;/p&gt;
&lt;h2&gt;How it works&lt;/h2&gt;
&lt;p&gt;Earlier experiments with replay capturing in the Unreal SDK leaned on platform vendor features (e.g. GDK&amp;#39;s game DVR on Xbox). That works, but every platform behaves differently and some can&amp;#39;t ship at all.&lt;/p&gt;
&lt;p&gt;For the new desktop and mobile implementation we went the other way and built the recording pipeline out of Unreal-native facilities so the same code runs everywhere the engine does. It has three main players:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The backbuffer capture&lt;/strong&gt; hooks Slate&amp;#39;s &lt;code&gt;OnBackBufferReadyToPresent&lt;/code&gt; on the render thread, throttles to the configured capture framerate (default 30 FPS, if your game can render faster frames are sampled down), and copies each frame into a texture the encoder can consume. Backbuffers come in many formats (e.g. 10-bit, HDR) and Slate doesn&amp;#39;t create them as shader resources, so the capture path does a hardware copy into a scratch texture and lets an engine pixel shader handle format conversion. If you&amp;#39;re already rendering BGRA8, it degenerates into a straight GPU copy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The video encoder&lt;/strong&gt; runs on a dedicated thread and feeds captured frames to a hardware H.264 encoder via Unreal&amp;#39;s &lt;code&gt;AVCodecs&lt;/code&gt; plugin family (NVENC on NVIDIA, AMF on AMD, VideoToolbox on Apple). The right encoder is picked at runtime based on the player&amp;#39;s GPU. Encoded output is grouped into short fragments at keyframe boundaries and muxed into fragmented MP4.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The replay recorder&lt;/strong&gt; orchestrates the whole thing. It maintains a rolling ring buffer of completed fragments only ever keeping the configured replay window and periodically assembles them into a complete, playable MP4 on disk. Each snapshot is written to a temp file and atomically renamed, so there&amp;#39;s never a torn, half-written video lying around.&lt;/p&gt;
&lt;p&gt;When a crash happens, the Unreal SDK picks up the latest rolling clip along with the crash report, wraps it into an envelope, and handles the upload to Sentry.&lt;/p&gt;
&lt;p&gt;If the player&amp;#39;s machine has no compatible hardware encoder, the feature self-disables for the session and logs a warning, so crash reporting itself is never affected.&lt;/p&gt;
&lt;h2&gt;What does it cost you at runtime?&lt;/h2&gt;
&lt;p&gt;&amp;quot;Continuously encode video of my game&amp;quot; sounds like the kind of thing that eats a game&amp;#39;s frame budget for breakfast. So we measured it.&lt;/p&gt;
&lt;p&gt;We profiled our &lt;a href=&quot;https://github.com/getsentry/unreal-tower&quot;&gt;Unreal demo game&lt;/a&gt; with the engine&amp;#39;s CSV profiler - once with replay off as a baseline and once with it on. We ran it at 4K fullscreen deliberately, as a bigger backbuffer means a bigger copy and encode cost, so 4K marks the worst case and the overhead at lower resolutions only goes down.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Baseline&lt;/th&gt;
&lt;th&gt;Replay On&lt;/th&gt;
&lt;th&gt;Δ&lt;/th&gt;
&lt;th&gt;Δ %&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Frame Time&lt;/td&gt;
&lt;td&gt;5.56 ms&lt;/td&gt;
&lt;td&gt;5.77 ms&lt;/td&gt;
&lt;td&gt;+0.20 ms&lt;/td&gt;
&lt;td&gt;+3.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Render Thread&lt;/td&gt;
&lt;td&gt;5.56 ms&lt;/td&gt;
&lt;td&gt;5.67 ms&lt;/td&gt;
&lt;td&gt;+0.11 ms&lt;/td&gt;
&lt;td&gt;+1.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Game Thread&lt;/td&gt;
&lt;td&gt;1.72 ms&lt;/td&gt;
&lt;td&gt;1.82 ms&lt;/td&gt;
&lt;td&gt;+0.10 ms&lt;/td&gt;
&lt;td&gt;+5.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU&lt;/td&gt;
&lt;td&gt;4.96 ms&lt;/td&gt;
&lt;td&gt;5.04 ms&lt;/td&gt;
&lt;td&gt;+0.08 ms&lt;/td&gt;
&lt;td&gt;+1.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RHI Thread&lt;/td&gt;
&lt;td&gt;1.81 ms&lt;/td&gt;
&lt;td&gt;1.82 ms&lt;/td&gt;
&lt;td&gt;+0.01 ms&lt;/td&gt;
&lt;td&gt;+0.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The bottom line: about 0.20 ms per frame at 4K. That&amp;#39;s ~1.2% of a 16.7 ms frame budget at 60 FPS, or ~2.4% at 120 FPS. Most of it comes from the render-thread backbuffer hook while encoding and file rotation happen on background threads, so the game thread, RHI thread, and GPU barely notice.&lt;/p&gt;
&lt;h2&gt;Getting started&lt;/h2&gt;
&lt;p&gt;Session Replay ships with the Sentry Unreal SDK 1.16.0, so if you already have the plugin set up, it&amp;#39;s one toggle away.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Enable the &lt;code&gt;AVCodecsCore&lt;/code&gt; engine plugin plus the codec plugin for each GPU vendor you want to cover (&lt;code&gt;NVCodecs&lt;/code&gt;, &lt;code&gt;AMFCodecs&lt;/code&gt; on Windows and Linux, &lt;code&gt;VTCodecs&lt;/code&gt; on Apple platforms). They all can live side by side since the right one is picked at runtime.&lt;/li&gt;
&lt;li&gt;In Project Settings &amp;gt; Plugins &amp;gt; Sentry, expand &amp;quot;Session Replay&amp;quot; and toggle &amp;quot;Enable session replay (experimental)&amp;quot;.&lt;/li&gt;
&lt;li&gt;Rebuild the plugin. Whether replay is compiled in is decided at build time, so after toggling the setting, make sure you start clean by deleting your project&amp;#39;s temporaries and rebuild.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That&amp;#39;s it. Replay duration, capture framerate, bitrate, and fragment length are all tunable (see the &lt;a href=&quot;https://docs.sentry.io/platforms/unreal/session-replay/&quot;&gt;Session Replay docs&lt;/a&gt; covering every knob, plus per-platform requirements).&lt;/p&gt;
&lt;p&gt;One important caveat: currently there is no automatic masking. The recording captures your rendered frames as-is, so player names, chat, and anything else on screen ends up in the video. Make sure the content your game renders is safe to record and that your privacy policy accounts for it.&lt;/p&gt;
&lt;h2&gt;Help us shape this&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://sentry.io/product/session-replay/&quot;&gt;Session Replay&lt;/a&gt; for Unreal Engine is an experimental feature, and a lot of what we build next depends on the feedback you give us.&lt;/p&gt;
&lt;p&gt;So, try it in your project, watch a few crashes with footage attached, and let us know what&amp;#39;s missing. &lt;a href=&quot;https://github.com/getsentry/sentry-unreal/issues&quot;&gt;Open an issue&lt;/a&gt; (or just react to an existing one) on GitHub or come chat on our &lt;a href=&quot;https://discord.gg/sentry&quot;&gt;Discord&lt;/a&gt;. Fewer &amp;quot;cannot reproduce&amp;quot; resolutions for everyone.&lt;/p&gt;
</content:encoded></item><item><title>From 57 bugs to 1, thanks to Seer</title><link>https://blog.sentry.io/57-bugs-to-1/</link><guid isPermaLink="true">https://blog.sentry.io/57-bugs-to-1/</guid><description>Learn how to automate bug fixes using Sentry, Seer, and cloud coding agents to clear your backlog.</description><pubDate>Thu, 23 Jul 2026 09:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Guest post&lt;/strong&gt; by Dan Mindru. Dan is an indie maker, enterprise consultant, and co-host of the &lt;a href=&quot;https://morningmakershow.com/&quot;&gt;Morning Maker Show&lt;/a&gt;. He maintains several open source projects and is the founder of &lt;a href=&quot;https://crontap.com&quot;&gt;Crontap&lt;/a&gt;, &lt;a href=&quot;https://saventify.com&quot;&gt;Saventify&lt;/a&gt;, and &lt;a href=&quot;http://mindrudan.com/startups&quot;&gt;about 10 other startups&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I was at the dentist the other day, getting ready for my appointment. The waiting room was pompously decorated. Each chair seemed to be from a different, expensive Danish designer. As I realize I&amp;#39;m about to get charged through the nose, I get a notification from my beloved Mail app.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;** ding **&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Screenshot of GitHub email notification&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It&amp;#39;s a new Pull Request on GitHub. This one is different though. I have no idea where it came from! As I scramble to figure out what automation triggered this, some intrusive thoughts start to take shape... Have I been hacked?!&lt;/p&gt;
&lt;p&gt;I open the code tab to find it&amp;#39;s a perfectly reasonable fix, including a unit test update and all. Not only that, it looks low risk and all checks are green too.&lt;/p&gt;
&lt;p&gt;As the dentist calls my name for probably the third time, I panic and (against my better judgment)... &lt;strong&gt;I&lt;/strong&gt; press &amp;quot;Squash and merge&amp;quot;.&lt;/p&gt;
&lt;p&gt;I put my phone back in my pocket and mumble to the dentist: &amp;quot;Sorry, I was in the zone...&amp;quot;.&lt;/p&gt;
&lt;h2&gt;I was never going to fix that bug&lt;/h2&gt;
&lt;p&gt;You&amp;#39;ve probably shipped to production from the dentist a lot more than I have, but to me this felt like a big shift. It&amp;#39;s not just that I merged some code. It was cheap code. Code that I didn&amp;#39;t need to work for or prioritize.&lt;/p&gt;
&lt;p&gt;So what happened? Where did that PR even come from? Hold on for a second.&lt;/p&gt;
&lt;p&gt;First, we need to have an honest chat about bugs and prioritization. If you&amp;#39;ve ever worked on any production system, issues will pile up and you&amp;#39;ll eventually end up having to triage bugs, usually asking these questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How many users are affected?&lt;/li&gt;
&lt;li&gt;What&amp;#39;s the impact?&lt;/li&gt;
&lt;li&gt;How long will it take to fix?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Based on this, a stakeholder will assign a priority to the bug. Usually everything that&amp;#39;s less than high goes into what they call &amp;quot;the backlog&amp;quot;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you&amp;#39;re new to this, &amp;quot;backlog&amp;quot; in enterprise is short for &lt;strong&gt;B&lt;/strong&gt;ottomless &lt;strong&gt;A&lt;/strong&gt;byss of &lt;strong&gt;C&lt;/strong&gt;asually &lt;strong&gt;K&lt;/strong&gt;ept, &lt;strong&gt;L&lt;/strong&gt;ong-&lt;strong&gt;O&lt;/strong&gt;verdue &lt;strong&gt;G&lt;/strong&gt;rievances.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But how much business value does fixing that bug bring compared to &amp;lt;&lt;em&gt;insert-feature-ceo-is-excited-about-here&lt;/em&gt;&amp;gt;? How much harm can that one bug really cause? After all, we had it for months.&lt;/p&gt;
&lt;p&gt;Indeed, the fate of this bug was sealed the moment it was compared to a higher priority feature. It&amp;#39;s not a problem of discipline or willpower, it&amp;#39;s a simple problem of economics. The cost of fixing it can&amp;#39;t be justified when compared to adding a feature that potentially brings heaps of new customers.&lt;/p&gt;
&lt;p&gt;Fast forward a few years, and you end up with (you guessed it) 57 bugs in the backlog.&lt;/p&gt;
&lt;p&gt;I&amp;#39;m excited to show you how we got it down to 1!&lt;/p&gt;
&lt;h2&gt;The economics of fixing bugs are changing&lt;/h2&gt;
&lt;p&gt;What I&amp;#39;ve described &lt;em&gt;used&lt;/em&gt; to be the case for most companies. I don&amp;#39;t think that&amp;#39;s true anymore. Around Spring 2026, a number of things came together in AI that I believe completely changed the economics of fixing bugs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;model intelligence&lt;/li&gt;
&lt;li&gt;larger context&lt;/li&gt;
&lt;li&gt;better harnesses &amp;amp; tool calling&lt;/li&gt;
&lt;li&gt;new abstractions (skills, hooks, sub/cloud agents, etc.)&lt;/li&gt;
&lt;li&gt;longer running agents&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this resulted in a leap that flipped the economics of fixing bugs from &amp;quot;not worth it&amp;quot; to &amp;quot;what the heck, let&amp;#39;s take this one into the sprint too&amp;quot;.&lt;/p&gt;
&lt;p&gt;In the midst of all this, I was happy to have had Sentry running in all of our systems.&lt;/p&gt;
&lt;p&gt;You see, all of the above is worthless if you don&amp;#39;t have &lt;em&gt;&lt;strong&gt;context&lt;/strong&gt;&lt;/em&gt;. With enough context, most bug fixes become trivial for AI to both fix &lt;em&gt;&lt;strong&gt;and&lt;/strong&gt;&lt;/em&gt; verify.&lt;/p&gt;
&lt;p&gt;And Sentry has the juiciest, most valuable context of them all: traces, device information, network, logs. Just to name a few. To my surprise, it even uses &lt;a href=&quot;https://docs.sentry.io/product/session-replay/&quot;&gt;Session Replays&lt;/a&gt; as context?!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;An issue in Sentry for a navigation error on &lt;a href=&quot;https://hunted.space&quot;&gt;hunted.space&lt;/a&gt;, with Seer &lt;a href=&quot;https://docs.sentry.io/product/ai-in-sentry/seer/autofix/&quot;&gt;Autofix&lt;/a&gt; open on the right showing an &amp;quot;Evidence&amp;quot; footer containing a &lt;a href=&quot;https://docs.sentry.io/product/session-replay/&quot;&gt;Session Replay&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Pro tip: the more telemetry you send (traces, profiles, logs), the better every automated fix downstream gets. I highly encourage you to set up &lt;a href=&quot;https://docs.sentry.io/platforms/javascript/logs/&quot;&gt;JavaScript logs in Sentry&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With this context a third of the job is done without even lifting a finger. Another third is implementing the fix. And the final third is verifying it.&lt;/p&gt;
&lt;p&gt;In theory, if we could automate all three thirds, we&amp;#39;d be able to close most bugs in our sleep. And that is exactly what I want to talk about in this post. I believe &lt;em&gt;most&lt;/em&gt; bugs can be solved automatically now.&lt;/p&gt;
&lt;p&gt;If bugfixing were a stock, it&amp;#39;d slide down like Lehman Brothers in &amp;#39;08. Except this time, the crash is the good news.&lt;/p&gt;
&lt;p&gt;Let me show you how to set this up.&lt;/p&gt;
&lt;h2&gt;How to fix your own bugs cheaply while you relax&lt;/h2&gt;
&lt;p&gt;To pick a correct setup, we need to first get into the mind of a bug. You need to &amp;quot;think like a bug&amp;quot;, if you will.&lt;/p&gt;
&lt;h3&gt;Bugs have feelings too&lt;/h3&gt;
&lt;p&gt;Not all bugs are created equal. The nice ones have a cool hairstyle and sunglasses. The bad ones have a mohawk and a tattoo. In general, we can sort bugs into 3 categories:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Isolated&lt;/th&gt;
&lt;th&gt;Reproducible&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;th&gt;Fix?&lt;/th&gt;
&lt;th&gt;Verification?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Friendly&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;A few surgical lines of code&lt;/td&gt;
&lt;td&gt;Unit test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grumpy&lt;/td&gt;
&lt;td&gt;Mostly&lt;/td&gt;
&lt;td&gt;Mostly&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Spans a few files and needs some digging&lt;/td&gt;
&lt;td&gt;Unit + Integration tests + Visual regression tests + Smoke tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Punk&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Sometimes&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Nobody knows; easier to rebuild the thing than reproduce&lt;/td&gt;
&lt;td&gt;Unit + Integration + E2E, DB checks, Visual regression tests, Plus human smoke tests&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Let&amp;#39;s take a look at each of these in detail and see how each can be fixed in the cheapest way possible.&lt;/p&gt;
&lt;h3&gt;Friendly bugs&lt;/h3&gt;
&lt;p&gt;These bugs are the ones that are easy to fix. They are isolated, reproducible, can be easily validated/reviewed and aren&amp;#39;t risky to merge.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;calling an API with invalid payload&lt;/li&gt;
&lt;li&gt;a missing null check that throws when a field is empty&lt;/li&gt;
&lt;li&gt;an off-by-one error that drops the last item in a list&lt;/li&gt;
&lt;li&gt;a layout breaking on mobile&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But don&amp;#39;t be fooled! As innocent as they look, these can be super frustrating to users and can lead to a bunch of support tickets too.&lt;/p&gt;
&lt;p&gt;I am happy to let you know that these bugs are solved.&lt;/p&gt;
&lt;p&gt;You can automate them away with this simple setup:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;[Pre-requisite]&lt;/strong&gt; Add Sentry to your application to get the required context (&lt;a href=&quot;https://docs.sentry.io/platforms/&quot;&gt;Sentry platform setup docs&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Connect to your &lt;a href=&quot;https://docs.sentry.io/integrations/source-code-mgmt/github/&quot;&gt;GitHub repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sentry.io/settings/seer/projects/&quot;&gt;Go to Seer settings&lt;/a&gt; and link your project to the corresponding repository(ies). This is what Seer reads for root cause analysis and code generation.&lt;/li&gt;
&lt;li&gt;On the same settings page, also set the Automation step to &lt;strong&gt;Stop after PR drafted&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;quot;Stop after PR drafted&amp;quot; is the most hands-off of the three automation levels. If you&amp;#39;d rather keep a human in the loop earlier, you can also stop after Root Cause (analysis only) or after Plan (suggested fix, no PR).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you can&amp;#39;t find Seer settings, make sure you have enabled &amp;quot;Generative AI Features&amp;quot; in your org&amp;#39;s &lt;a href=&quot;https://sentry.io/settings/organization/general/&quot;&gt;general settings&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And just like that, no more Friendly Bugs. How does this work in practice?&lt;/p&gt;
&lt;p&gt;From my perspective, I just get PRs every few days. They usually contain fixes spanning a few lines of code. I see if all checks are green and hit the merge button. That even closes the issue in Sentry so if you&amp;#39;re as lazy as I am, you&amp;#39;ll love it!&lt;/p&gt;
&lt;p&gt;A nice touch: Seer automatically parses your rules files (Cursor, Claude Code, etc.), so both the analysis and the handed-off fix follow the same conventions your own agents do.&lt;/p&gt;
&lt;p&gt;From Sentry&amp;#39;s perspective, it&amp;#39;s a more involved process. An issue has to earn its automatic fix:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the issue has enough events registered that it&amp;#39;s clearly not a fluke&lt;/li&gt;
&lt;li&gt;the issue is recent (currently, within the last 14 days)&lt;/li&gt;
&lt;li&gt;the issue has a sufficient fixability score: Sentry estimates how straightforward the issue is to resolve, and only confident ones proceed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In my experience, what this means is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You don&amp;#39;t get spammed with PRs.&lt;/li&gt;
&lt;li&gt;When a PR comes in it&amp;#39;s usually a slam dunk.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Just by enabling this, you&amp;#39;ve probably already fixed a massive chunk of your bugs. It&amp;#39;s quite remarkable. Let&amp;#39;s go deeper now!&lt;/p&gt;
&lt;h3&gt;Grumpy bugs&lt;/h3&gt;
&lt;p&gt;These bugs are harder to crack. They have more unknowns, and typically require deeper investigation. However, they are still mostly isolated and reproducible, only the fix spans multiple files now.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a race condition between two API calls that only shows up on slow connections&lt;/li&gt;
&lt;li&gt;state that goes stale after a websocket reconnect&lt;/li&gt;
&lt;li&gt;an optimistic UI update that never rolls back when the request fails&lt;/li&gt;
&lt;li&gt;a pagination bug that skips or duplicates items when data changes between pages&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are a lot harder to justify financially. Typically they&amp;#39;d be written off as &amp;quot;edge case&amp;quot; and deprioritized.&lt;/p&gt;
&lt;p&gt;If you expect me to write &amp;quot;but Seer also fixes these&amp;quot; and move on to the next section, I won&amp;#39;t. I can&amp;#39;t lie to you, Seer alone cannot fix these.&lt;/p&gt;
&lt;p&gt;Seer is a fantastic investigator, but it doesn&amp;#39;t have a full-blown environment to apply &amp;amp; verify the fix. It can&amp;#39;t spin up a dev server, can&amp;#39;t poke at a database, can&amp;#39;t press the button that triggers the bug 5 steps into the flow.&lt;/p&gt;
&lt;p&gt;By now we should know: if the agent can&amp;#39;t verify, the task is not done.&lt;/p&gt;
&lt;p&gt;So for Grumpy bugs, we let Seer do what it does best (root cause analysis with all that juicy production context) and then hand off the actual fixing to an agent that does have an environment: a &lt;a href=&quot;https://docs.sentry.io/integrations/coding-agents/&quot;&gt;full-blown cloud coding agent&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can currently select between:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://claude.ai/code&quot;&gt;Claude Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cursor.com/&quot;&gt;Cursor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;and &lt;a href=&quot;https://github.com/features/copilot&quot;&gt;GitHub Copilot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;agents, with more likely on the way. Let&amp;#39;s pick Cursor for this example.&lt;/p&gt;
&lt;p&gt;Here&amp;#39;s the setup:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In Sentry, go to Settings → Integrations → &amp;quot;Cursor Agent&amp;quot;, and click &amp;quot;Accept &amp;amp; Install&amp;quot;.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: there&amp;#39;s also a Cursor API integration. Make sure to pick the one that has the &amp;quot;Coding Agent&amp;quot; tag.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;p&gt;Head over to &lt;a href=&quot;https://cursor.com/dashboard/api?section=user-keys#user-api-keys&quot;&gt;Cursor Account Settings&lt;/a&gt; and generate an API Key under API Keys in your profile.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go back to Sentry and paste the API key.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, go to &lt;a href=&quot;https://sentry.io/settings/seer/projects/&quot;&gt;Seer settings&lt;/a&gt;, select your project and set Handoff to Agent to &amp;quot;Cursor Cloud Agent&amp;quot; and Automation Steps to &amp;quot;Stop after PR drafted&amp;quot;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Seer Settings with a project that has Handoff to Agent and Automation Steps set to the described values&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you&amp;#39;d like more control over third-party Cloud Agents, you can keep Seer as the &amp;quot;Handoff Agent&amp;quot; and trigger the third-party Cloud Agent manually on a per-issue basis.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On any issue&amp;#39;s Root Cause Analysis card, click the dropdown next to &amp;quot;Find Solution&amp;quot; and launch a Cursor Cloud Agent. Great for when you want to delegate one specific bug instead of automating everything.&lt;/p&gt;
&lt;h3&gt;What really happened at the dentist&lt;/h3&gt;
&lt;p&gt;Now you&amp;#39;re finally ready for the big reveal. Why did I get that PR at the dentist?&lt;/p&gt;
&lt;p&gt;Turns out, a colleague happened to enable the Cursor Agent in Seer settings a few days before. Sentry determined the bug was worth fixing, handed it off to Cursor, and after implementing and verifying the fix, it opened a PR (more on this handoff in a minute; it&amp;#39;s the key to the next category of bugs too).&lt;/p&gt;
&lt;p&gt;The confusing part was that there was no link back to Sentry, and I couldn&amp;#39;t see the Cursor Agent chat either, hence my initial panic. Checking this on my small screen didn&amp;#39;t help either, as I missed some of the PR metadata I&amp;#39;d usually look for on a larger screen.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;GitHub Pull Request overview with the issue description summary and root cause&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So, how does the handoff to the coding agent work?&lt;/p&gt;
&lt;p&gt;Seer&amp;#39;s Autofix flow has three steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Root Cause Analysis&lt;/li&gt;
&lt;li&gt;Solution Identification&lt;/li&gt;
&lt;li&gt;Code Generation&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With a handoff configured, Seer still performs the root cause analysis and gathers that juicy production context (stack traces, breadcrumbs, traces etc.), but instead of generating code itself, step three goes to the Cursor Cloud Agent. The prompt will contain everything Seer found so far.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Cursor Cloud Agent Prompt containing root cause analysis&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The agent then checks out your repo in a real environment where it can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;run your unit and integration tests&lt;/li&gt;
&lt;li&gt;spin up a dev server and actually exercise the broken flow&lt;/li&gt;
&lt;li&gt;use any MCP from the &lt;a href=&quot;https://cursor.com/marketplace&quot;&gt;Cursor marketplace&lt;/a&gt; to go further: query a dev database, drive a browser, check CI, write and execute E2E tests, take screenshots etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In other words, it closes the loop. Investigate → fix → verify. That last third of the job, the expensive one that used to be handed over to a human, is now also automated.&lt;/p&gt;
&lt;p&gt;With this in place, you&amp;#39;re likely down to 80-90% of bugs being fixed. Now it&amp;#39;s time for the boss level. Things won&amp;#39;t be as smooth from here, let&amp;#39;s take a look.&lt;/p&gt;
&lt;h3&gt;Punk bugs&lt;/h3&gt;
&lt;p&gt;These are the bugs that take a good look at your test suite and burst into laughter. They&amp;#39;re not isolated, they&amp;#39;re barely reproducible, and the root cause usually lives somewhere AI can&amp;#39;t casually wander in, like production data or a third-party service.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a crash caused by old production data: records created by a migration 5 years ago, in a shape your current code could never produce&lt;/li&gt;
&lt;li&gt;a bug that only triggers for one customer because of their specific combination of feature flags, plan, and account age&lt;/li&gt;
&lt;li&gt;behavior that depends on a third-party service doing something undocumented (an auth provider, a payment webhook arriving twice)&lt;/li&gt;
&lt;li&gt;an issue that only shows up at production scale: a query that&amp;#39;s fine with 10k rows and pathological with 50 million&lt;/li&gt;
&lt;li&gt;something environmental: a corporate proxy, production config, a browser extension, a device you don&amp;#39;t own&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The missing piece is production. If you made it this far down the article, you&amp;#39;re probably clever enough to know we should never, under any circumstances, give AI access to production.&lt;/p&gt;
&lt;p&gt;The best way to fix these is to have the agent work with you. Typically, this will involve you cloning some records to dev, observing the agent and generally taking the time to understand what&amp;#39;s going wrong.&lt;/p&gt;
&lt;p&gt;The context is still just as valuable though, so how do we get it?&lt;/p&gt;
&lt;p&gt;The setup for these bugs is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add the hosted &lt;a href=&quot;https://mcp.sentry.dev/&quot;&gt;Sentry MCP&lt;/a&gt; server to your favorite IDE or agentic CLI. In Cursor that&amp;#39;s Settings → MCP → New MCP Server (or just edit mcp.json in most agentic coding tools):&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;mcpServers&amp;quot;: {
    &amp;quot;sentry&amp;quot;: {
      &amp;quot;url&amp;quot;: &amp;quot;https://mcp.sentry.dev/mcp&amp;quot;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;p&gt;Log in to your Sentry org via OAuth. Click through and you&amp;#39;re done.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open the agent chat and start asking questions in plain English: &amp;quot;I would like to investigate the root cause of a production bug. Pull the events for SAVENTIFY-1R from Sentry and look at the breadcrumbs&amp;quot;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Using the &lt;a href=&quot;https://mcp.sentry.dev/&quot;&gt;Sentry MCP&lt;/a&gt; to investigate a &lt;a href=&quot;https://saventify.com&quot;&gt;Saventify&lt;/a&gt; bug in &lt;a href=&quot;https://cursor.com/&quot;&gt;Cursor&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The MCP server gives your agent direct access to everything it needs to investigate and propose a fix, including Seer that can run a tool to root cause analyze right from the chat.&lt;/p&gt;
&lt;p&gt;As the agent pulls the context, you contribute with historical data and business logic that&amp;#39;s not obvious:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;Those accounts were migrated from the old system in 2021 and have a different config&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;We changed payment providers that quarter&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;That customer is behind a corporate proxy&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of these one-liners will dramatically increase your chances of finding the correct root cause. Once you do, the fix can be fully automated and sent off to a Cloud Agent to fix if it can also be reproduced on a dev environment. Otherwise, you&amp;#39;ll need to manually verify the fix.&lt;/p&gt;
&lt;p&gt;Because of this, these bugs will always be expensive to fix. In some cases, they can only be fixed by a person that happens to have specific historical/business context. Realistically, no amount of AI can get around it, and I suppose that&amp;#39;s why building software can be a blessing and a curse at the same time.&lt;/p&gt;
&lt;p&gt;Reducing investigation and verification time is the best we can hope for, and this workflow does exactly that.&lt;/p&gt;
&lt;h2&gt;What was that one bug you couldn&amp;#39;t fix?&lt;/h2&gt;
&lt;p&gt;I know this is what you&amp;#39;ve all been waiting for.&lt;/p&gt;
&lt;p&gt;&amp;quot;If you&amp;#39;re so clever at explaining this bullet proof setup, Dan, then how come you didn&amp;#39;t fix all your bugs?&amp;quot;&lt;/p&gt;
&lt;p&gt;This boils down to cost and a high risk / low reward combination. Just to reproduce, this bug requires:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A production MSAL config&lt;/li&gt;
&lt;li&gt;A Microsoft failed request + redirect and&lt;/li&gt;
&lt;li&gt;Timing to be just right in order to reproduce.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short, users experience this bug as data loss, which is catastrophic in my eyes. When they perform certain create/update actions and their auth token expires, MSAL sometimes fails to perform a silent refresh token call, and the application will go through a series of redirects that drops the redirect URL together with the data they entered.&lt;/p&gt;
&lt;p&gt;This has been so hard to fix that we&amp;#39;ve contemplated switching auth providers. A few times now. It might be cheaper and more reliable at this point.&lt;/p&gt;
&lt;p&gt;In the meantime, we had to take a step back and think outside of the box. After many failed attempts at a fix, we ended up implementing save/restore on every keystroke, so at least users can pick up where they left off next time they access that URL.&lt;/p&gt;
&lt;h2&gt;Wrapping things up&lt;/h2&gt;
&lt;p&gt;It&amp;#39;s hard to say what makes people emotionally attached to a product. It&amp;#39;s usually a mix of branding, design, features, performance, customer support and ultimately: value for money.&lt;/p&gt;
&lt;p&gt;However, it&amp;#39;s a lot easier to figure out when people start to hate a product: If you introduce enough paper cuts, they will start to think they can&amp;#39;t trust your app anymore. Not addressing bugs is risky business. Once a certain threshold is crossed, there&amp;#39;s not much you can do to turn it around. Even if the mistake is on their side, they&amp;#39;ll still attribute it to your product.&lt;/p&gt;
&lt;p&gt;This will leave users more and more frustrated, and ultimately make them recommend against your product. That&amp;#39;s the absolute worst case scenario.&lt;/p&gt;
&lt;p&gt;Luckily, most bugs are not like this.&lt;/p&gt;
&lt;p&gt;Thanks to recent AI advancements, Sentry and Seer, most bugs can now be fixed at a fraction of the cost. I strongly encourage you to try it, you won&amp;#39;t look back!&lt;/p&gt;
</content:encoded></item><item><title>.NET support for Godot is now generally available</title><link>https://blog.sentry.io/godot-dotnet-ga/</link><guid isPermaLink="true">https://blog.sentry.io/godot-dotnet-ga/</guid><description>Sentry&apos;s Godot Engine SDK 2.0.0 brings C#/.NET error reporting and application metrics to general availability.</description><pubDate>Wed, 22 Jul 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We recently released version 2.0.0 of &lt;a href=&quot;https://github.com/getsentry/sentry-godot/releases&quot;&gt;Sentry&amp;#39;s Godot Engine SDK&lt;/a&gt;, bringing C#/.NET support and application metrics to general availability. That means you can now capture errors from your managed C# code and track custom metrics across your development and retail builds.&lt;/p&gt;
&lt;h2&gt;C#/.NET support&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://godotengine.org/&quot;&gt;Godot&lt;/a&gt; supports two fairly distinct worlds of scripting: GDScript and C#. During the 1.x releases, we instrumented the SDK to capture issues from GDScript, the engine itself, and the native C/C++ code, and expanded support to all officially supported platforms. With the 2.0.0 release, we&amp;#39;re introducing first-class error reporting for .NET in Godot Engine.&lt;/p&gt;
&lt;p&gt;Errors from your managed code now appear on the same trace alongside GDScript and engine issues, and scope elements like tags, breadcrumbs, and user context are present where they&amp;#39;re currently synchronized.&lt;/p&gt;
&lt;p&gt;So, let&amp;#39;s see how this works in practice. Here&amp;#39;s a short version of C# code that throws at runtime in our Godot game:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private List? TryFetchUpgradesFromServer()
{
    var fetchTransaction = SentrySdk.StartTransaction(&amp;quot;fetch_upgrades&amp;quot;, &amp;quot;http.client&amp;quot;);
    return ParseUpgradeData(FetchUpgradeDataFromServer(fetchTransaction), fetchTransaction);
}

private List? ParseUpgradeData(string responseContent, ITransactionTracer transaction)
{
    // ...

    // Parsing the server response throws JsonException
    var serverResponse = JsonSerializer.Deserialize(responseContent);
    var upgradePaths = new List(serverResponse!.upgrades.Length);

    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The SDK captures the error at runtime and sends an envelope to your Sentry project. This is how the issue looks in Sentry:&lt;/p&gt;
&lt;p&gt;Sentry has integrations for most popular SCMs, like GitHub, and can fetch the &lt;a href=&quot;https://docs.sentry.io/platforms/godot/configuration/source-context/&quot;&gt;source code context&lt;/a&gt; around each frame right from your repository.&lt;/p&gt;
&lt;h2&gt;Metrics&lt;/h2&gt;
&lt;p&gt;To better understand performance across different hardware configurations, you can send &lt;a href=&quot;https://sentry.io/product/metrics/&quot;&gt;metrics&lt;/a&gt; to Sentry so you can track performance over time and slice it by custom attributes. In this example, we measure the actual time between frames instead of relying on the engine&amp;#39;s delta time, since delta time is typically clamped and smoothed. We also attach CPU and GPU attributes, making it easy to slice the data by hardware configuration. You can add other attributes to narrow the data down even further, such as the game level where the metric was recorded.&lt;/p&gt;
&lt;p&gt;Here we&amp;#39;re looking at the p95 frame time to spot configurations where the game consistently struggles to run smoothly. This makes it easier to identify hardware-specific performance issues, or even level-specific bottlenecks, and focus your optimization efforts where they&amp;#39;ll have the biggest impact.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-gdscript&quot;&gt;extends Node
## Samples frame time and sends it to Sentry as metrics.

const FRAME_SAMPLE_INTERVAL_MS := 100.0

var _time_since_sample: float = 0.0
var _last_frame_usec: int = Time.get_ticks_usec()

func _process(_delta: float) -&amp;gt; void:
    var now := Time.get_ticks_usec()
    var frame_ms := (now - _last_frame_usec) / 1000.0
    _last_frame_usec = now
    _time_since_sample += frame_ms

    # Sample at a fixed interval to avoid producing too many metrics.
    if _time_since_sample &amp;gt;= FRAME_SAMPLE_INTERVAL_MS:
        _time_since_sample = 0.0
        var attributes := {
            &amp;quot;gpu.name&amp;quot;: RenderingServer.get_video_adapter_name(),
            &amp;quot;cpu.name&amp;quot;: OS.get_processor_name(),
            &amp;quot;cpu.cores&amp;quot;: OS.get_processor_count(),
        }
        SentrySDK.metrics.distribution(&amp;quot;perf.frame_time&amp;quot;, frame_ms,
                SentryUnit.millisecond, attributes)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For another example, let&amp;#39;s track how long runs take and what experience level players reach on average in a roguelite/survivors-style game. &lt;code&gt;game.run.time&lt;/code&gt; records the total time it took to complete a single run, while &lt;code&gt;game.run.xp_level&lt;/code&gt; records the experience level reached during that run.&lt;/p&gt;
&lt;p&gt;This example shows that metrics can be used to track more than just performance: they&amp;#39;re also useful for gameplay events. Data like this can help you balance gameplay variables and create a better experience for your players.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-gdscript&quot;&gt;extends Node
## Sends gameplay metrics to Sentry by listening for game events
## on the EventBus.

func _ready() -&amp;gt; void:
    EventBus.connect(&amp;quot;EnemyDestroyed&amp;quot;, _on_enemy_destroyed)
    EventBus.connect(&amp;quot;RunEnded&amp;quot;, _on_run_ended)

func _on_enemy_destroyed(_score_value: int) -&amp;gt; void:
    SentrySDK.metrics.count(&amp;quot;game.enemies_killed&amp;quot;, 1)

func _on_run_ended(xp_level: int, time: float) -&amp;gt; void:
    SentrySDK.metrics.distribution(&amp;quot;game.run.xp_level&amp;quot;, xp_level)
    SentrySDK.metrics.distribution(&amp;quot;game.run.time&amp;quot;, time, SentryUnit.second)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Try it out&lt;/h2&gt;
&lt;p&gt;Give it a try! We want to hear your feedback as we continue to improve the SDK. To get started, download Sentry from the &lt;a href=&quot;https://store.godotengine.org/asset/sentry/sentry/&quot;&gt;Godot Asset Store&lt;/a&gt;, or directly from &lt;a href=&quot;https://github.com/getsentry/sentry-godot/releases&quot;&gt;GitHub&lt;/a&gt;. Extract the archive into your project, set your DSN in the project settings under &lt;code&gt;Sentry &amp;gt; Options&lt;/code&gt;, and let the errors come through with full context. For more details, check out the dedicated &lt;a href=&quot;https://docs.sentry.io/platforms/godot/dotnet/&quot;&gt;C#/.NET guide&lt;/a&gt;. Happy debugging!&lt;/p&gt;
</content:encoded></item><item><title>How to structure a log</title><link>https://blog.sentry.io/structure-a-log/</link><guid isPermaLink="true">https://blog.sentry.io/structure-a-log/</guid><description>A practical guide to writing structured, queryable, lint-enforceable logs with consistent event names, attributes, severity, and schema checks.</description><pubDate>Tue, 21 Jul 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You&amp;#39;ve decided to step up your logging game and start sending more valuable, structured logs that you can query, aggregate, and use for debugging in production. &lt;strong&gt;&lt;em&gt;Go, you!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now, uh, how do you actually write them?&lt;/p&gt;
&lt;p&gt;We&amp;#39;re not going to spend much time on &lt;em&gt;what&lt;/em&gt; you should log. We&amp;#39;ve &lt;a href=&quot;https://blog.sentry.io/logging-best-practices/&quot;&gt;covered that&lt;/a&gt; already, &lt;a href=&quot;https://www.youtube.com/watch?v=o0Xna6xo8TU&quot;&gt;a few&lt;/a&gt; times &lt;a href=&quot;https://docs.sentry.io/platforms/javascript/logs/#best-practices&quot;&gt;before&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: collect useful debugging context as code executes, then emit &lt;a href=&quot;https://docs.sentry.io/platforms/javascript/logs/#wide-events-over-scattered-logs&quot;&gt;wide-event logs&lt;/a&gt; at meaningful milestones. Always emit a final outcome event on both success and failure paths.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What we will be covering is &lt;em&gt;how&lt;/em&gt; to actually write those logs, answering questions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What shape should they actually be in?&lt;/li&gt;
&lt;li&gt;How do we make them useful for querying and filtering?&lt;/li&gt;
&lt;li&gt;How do we know the logs we write will be useful for debugging and understanding what happened?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What makes a log structured is not just pairing messages with arbitrary JSON objects.&lt;/p&gt;
&lt;p&gt;Instead, we treat logs like real application data that we need to be able to search, filter, and aggregate to understand broad trends and debug specific incidents.&lt;/p&gt;
&lt;p&gt;How we choose to actually write those logs will greatly impact how useful they are for debugging and understanding what happened.&lt;/p&gt;
&lt;p&gt;Here is my &lt;strong&gt;&lt;em&gt;very opinionated&lt;/em&gt;&lt;/strong&gt; guide (as someone who spends time with Sentry logs users and helps them get more from their logging) to one way to write structured logs.&lt;/p&gt;
&lt;h2&gt;The shape of a structured log&lt;/h2&gt;
&lt;p&gt;The exact convention you decide to use matters less than whether it is predictable and applied consistently.
Once you define a pattern and stick to it, you should be able to locate and understand any event in your system based on its log message and attributes.&lt;/p&gt;
&lt;p&gt;The convention I use in my projects looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;logger.warn(&amp;#39;payment.capture&amp;#39;, {
  &amp;#39;payment.id&amp;#39;: payment.id,
  &amp;#39;payment.amount_cents&amp;#39;: payment.amountCents,
  &amp;#39;payment.currency&amp;#39;: payment.currency,
  &amp;#39;payment.result&amp;#39;: &amp;#39;failed&amp;#39;,
  &amp;#39;payment.failure.reason_code&amp;#39;: reasonCode,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few patterns you&amp;#39;ll notice in the example above, and I&amp;#39;ll explain in more detail below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Event names use a predictable pattern of &lt;code&gt;domain.action&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;snake_case&lt;/code&gt; segments, regardless of the programming language or framework you&amp;#39;re using. One casing convention across services just makes querying later so much easier.&lt;/li&gt;
&lt;li&gt;Flattened attribute objects using dot notation, not nested objects.&lt;/li&gt;
&lt;li&gt;Predictable &lt;code&gt;result&lt;/code&gt; attributes with low-cardinality values like &lt;code&gt;succeeded&lt;/code&gt;, &lt;code&gt;failed&lt;/code&gt;, &lt;code&gt;retried&lt;/code&gt;, &lt;code&gt;canceled&lt;/code&gt;, or &lt;code&gt;completed&lt;/code&gt; for visualizing and grouping.&lt;/li&gt;
&lt;li&gt;An expected or recoverable failure uses a warning log level.&lt;/li&gt;
&lt;li&gt;Attributes should only contain primitive values or arrays of primitive values. No objects, including shallow objects, or arrays of objects.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Enforce log patterns with ESLint&lt;/h2&gt;
&lt;p&gt;I use a personal ESLint plugin for this to help enforce these patterns consistently across my TypeScript projects.&lt;/p&gt;
&lt;p&gt;This is &lt;em&gt;not&lt;/em&gt; an official Sentry plugin; these are just my opinionated guidelines.
It is a logging-library-agnostic way to loosely enforce the shape of your logs.
This does not go as far as, for example, validating &lt;code&gt;zod&lt;/code&gt; schemas or similar.&lt;/p&gt;
&lt;p&gt;If you want to follow along with my patterns, use the prompts below to install and configure my rules for your project.
If you want to do something a little different, I highly recommend creating your own ESLint plugin or equivalent linting/tooling for your language to enforce your own patterns.
Your agent/assistant can help you create whatever you need. Try running this prompt to customize your own rules for whichever language or framework you&amp;#39;re using.&lt;/p&gt;
&lt;p&gt;To install &lt;em&gt;my&lt;/em&gt; ESLint plugin, give this prompt to your AI assistant:&lt;/p&gt;
&lt;h2&gt;Use stable event names&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s start with what a bad log might look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;console.log(`${user.name} logged in`, {
  userUuid: user.uuid,
  userName: user.name,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Technically, this is structured; it has a message and a data object. But the message is not stable. Every successful login creates a different log event for each user name, which makes the logs harder to query, group, and alert on.&lt;/p&gt;
&lt;p&gt;A useful structured log needs a predictable event name. Dynamic data belongs in attributes, not in the message.&lt;/p&gt;
&lt;p&gt;A better log message with a stable and predictable event name might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;logger.info(&amp;#39;auth.login&amp;#39;, {
  &amp;#39;auth.result&amp;#39;: &amp;#39;succeeded&amp;#39;,
  &amp;#39;user.uuid&amp;#39;: user.uuid,
  &amp;#39;user.plan.tier&amp;#39;: user.plan.tier,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This still communicates the event easily to humans and is easy to remember and query.
Avoid logging usernames, and email addresses. Depending on your data policy you may or may not be able to include an identifier like a UUID.&lt;/p&gt;
&lt;p&gt;For most application events, I like to segment the event name into two parts: the &lt;em&gt;domain&lt;/em&gt; and the &lt;em&gt;action&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;domain.action
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example: &amp;quot;&lt;code&gt;auth.login&lt;/code&gt;&amp;quot;, &amp;quot;&lt;code&gt;payment.capture&lt;/code&gt;&amp;quot;, &amp;quot;&lt;code&gt;webhook.delivery&lt;/code&gt;&amp;quot;, or &amp;quot;&lt;code&gt;cart.checkout&lt;/code&gt;&amp;quot;.&lt;/p&gt;
&lt;p&gt;The &amp;quot;domain&amp;quot; is fairly arbitrary, but I think of it as the closest context object you would want associated with it.
As you &lt;a href=&quot;https://docs.sentry.io/platforms/javascript/logs/#shared-attributes&quot;&gt;collect context&lt;/a&gt; through your application, there are natural boundaries where you typically scope attributes.&lt;/p&gt;
&lt;p&gt;When the user signs in, you might add &lt;code&gt;auth.*&lt;/code&gt; attributes to the context. On a cart page, you might add &lt;code&gt;cart.*&lt;/code&gt; attributes.&lt;/p&gt;
&lt;p&gt;At checkout, the log event itself can describe the operation that finally happened, like &lt;code&gt;cart.checkout&lt;/code&gt; with event-specific attributes. The event attributes might be the result of the checkout, as well as all of the context leading up to it, creating a debugging paper trail.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To enforce that convention with ESLint, configure the event-name rules like this:&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Use scoped attribute keys&lt;/h2&gt;
&lt;p&gt;Event names describe what happened: &lt;code&gt;payment.capture&lt;/code&gt;, &lt;code&gt;auth.login&lt;/code&gt;, &lt;code&gt;cart.checkout&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Attribute keys describe the facts you want to query about that event: &lt;code&gt;payment.result&lt;/code&gt;, &lt;code&gt;payment.amount_cents&lt;/code&gt;, &lt;code&gt;auth.org_id&lt;/code&gt;, &lt;code&gt;retry.attempt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Use the same scoped dot-notation style for attributes, but think of attribute keys differently than event names. Event names are actions. Attribute keys are dimensions.&lt;/p&gt;
&lt;p&gt;Think of each scoped key as a future question you are making cheap to answer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;payment.result              -&amp;gt; Did it succeed or fail?
payment.failure.reason_code -&amp;gt; Why did it fail?
payment.amount_cents        -&amp;gt; How much money was involved?
user.id                     -&amp;gt; Which customer was affected?
retry.attempt               -&amp;gt; Was this an early failure or repeated failure?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If a field might become a filter, grouping, dashboard dimension, alert condition, or incident-debugging clue, it deserves a stable scoped key.&lt;/p&gt;
&lt;p&gt;A nested object preserves how your application stores data. A flat log event exposes the small set of fields you expect to query, group, alert on, and trust later.&lt;/p&gt;
&lt;p&gt;Dot notation gives you some of the organization of nested data while keeping each field directly addressable. &lt;code&gt;payment.failure.reason_code&lt;/code&gt; still feels organized like JSON, but as a string key, the value is immediately accessible without further parsing.&lt;/p&gt;
&lt;p&gt;Scoped keys also help enforce safer logging practices. By manually defining the keys we want to log, we can ensure that we are only logging the data we intend to log, and not any other data that may be present in an arbitrary object.&lt;/p&gt;
&lt;h3&gt;Event scoped attributes&lt;/h3&gt;
&lt;p&gt;Once you have a naming convention for attributes, the next question is where those attributes should be attached.&lt;/p&gt;
&lt;p&gt;Throughout the application, you should be &lt;a href=&quot;https://docs.sentry.io/platforms/javascript/logs/#add-context&quot;&gt;adding context to your logs&lt;/a&gt; at natural boundaries that create a useful timeline of application state.
I mentioned before adding &lt;code&gt;auth.*&lt;/code&gt; attributes when the user was authenticated.&lt;/p&gt;
&lt;p&gt;Different logging libraries handle context differently. For Sentry JavaScript SDK 10.32 or newer, use an isolation scope for request-specific context. Scope attributes must be strings, numbers, or booleans.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;Sentry.getIsolationScope().setAttributes({
  &amp;#39;auth.org_id&amp;#39;: user.orgId,
  &amp;#39;auth.user_tier&amp;#39;: user.tier,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every log emitted while that isolation scope is active receives the same &lt;code&gt;auth.*&lt;/code&gt; attributes. Add only deliberate, policy-approved values: shared context is propagated broadly and is not a safeguard against sensitive-data collection.&lt;/p&gt;
&lt;p&gt;On the log itself, we finally log attributes that detail the &amp;quot;action&amp;quot; that happened, along with any other useful information we can capture that may be useful for debugging.
This typically includes a result for the action, and any application state that led up to it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;logger.info(&amp;#39;cart.checkout&amp;#39;, {
  &amp;#39;checkout.result&amp;#39;: &amp;#39;failed&amp;#39;,
  &amp;#39;checkout.failure.reason_code&amp;#39;: reasonCode,
  &amp;#39;cart.coupon_code&amp;#39;: couponCode,
  &amp;#39;cart.total_cents&amp;#39;: cart.totalAmount,
  &amp;#39;cart.total_items&amp;#39;: cart.totalItems,
  &amp;#39;cart.item_ids&amp;#39;: cart.itemIds, // scalar array of id strings
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;To enforce scoped attribute keys with ESLint, configure the attribute-key rule like this:&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Keep event attributes inline&lt;/h2&gt;
&lt;p&gt;Spreads and helpers can make logs more consistent, but they &lt;em&gt;usually&lt;/em&gt; belong at the context boundary, not inside every event log.&lt;/p&gt;
&lt;p&gt;Shared contextual attributes like &amp;quot;&lt;code&gt;auth.org_id&lt;/code&gt;&amp;quot;, &amp;quot;&lt;code&gt;auth.user_tier&lt;/code&gt;&amp;quot;, or &amp;quot;&lt;code&gt;flags.name&lt;/code&gt;&amp;quot; are often useful on many logs.
Rather than spreading them into every event log, set them through your logger&amp;#39;s context mechanism. For Sentry, use the isolation-scope pattern above. This keeps event attributes explicit, but every shared value still needs privacy and retention review before it is propagated.&lt;/p&gt;
&lt;p&gt;Most attributes should describe the event itself and use its event namespace. Add a small number of surrounding contextual namespaces only when they materially help explain, filter, or investigate the event.&lt;/p&gt;
&lt;p&gt;Keeping attributes inline makes it easier to review the log event where it happened, helps prevent unknown attributes from sneaking in, and ensures the log event is self-contained and easy to understand.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;logger.warn(&amp;#39;payment.capture&amp;#39;, {
  &amp;#39;payment.id&amp;#39;: payment.id,
  &amp;#39;payment.amount_cents&amp;#39;: payment.amountCents,
  &amp;#39;payment.currency&amp;#39;: payment.currency,
  &amp;#39;payment.result&amp;#39;: &amp;#39;failed&amp;#39;,
  &amp;#39;payment.failure.reason_code&amp;#39;: reasonCode,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Avoid this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const paymentAttributes = getPaymentLogAttributes(payment);

logger.warn(&amp;#39;payment.capture&amp;#39;, {
  ...paymentAttributes,
  &amp;#39;payment.result&amp;#39;: &amp;#39;failed&amp;#39;,
  &amp;#39;payment.failure.reason_code&amp;#39;: reasonCode,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I keep this rule to help enforce consistency, but there isn&amp;#39;t necessarily anything inherently wrong with using shared attributes or helpers to attach attributes to logs.
You just need to remain consistent and be mindful about what you&amp;#39;re logging. If you were using a schema validation tool, this would be less of a concern.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To enforce explicit attributes with ESLint, configure the inline-attributes rule like this:&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Use primitive attribute values&lt;/h2&gt;
&lt;p&gt;Structured logs are most useful when attributes are easy to query, filter, group, and aggregate. That starts with formatting values in ways your logging backend can index and search predictably.&lt;/p&gt;
&lt;p&gt;Along with a flat attribute key structure, I prefer to limit attribute values to primitives: strings, numbers, booleans, and arrays of those primitives.&lt;/p&gt;
&lt;p&gt;The plugin can always reject values that are visibly non-primitive in the source, such as inline object literals and arrays of objects. Its default, non-type-aware behavior allows expressions whose runtime type is unknown, including identifiers and member expressions. Turning on &lt;code&gt;disallowUnknownAttributeValues&lt;/code&gt; rejects those unknown expressions too, so use that option only if your project intentionally accepts the resulting false positives for ordinary dynamic values.&lt;/p&gt;
&lt;p&gt;Logging raw objects is tempting when debugging because they preserve the full detail, but they usually add noise and cost without much query value. Nested objects vary wildly across code paths. Arrays of objects are even worse: they can explode the size of a log line while still being awkward to search.&lt;/p&gt;
&lt;p&gt;Avoid this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;logger.warn(&amp;#39;webhook.delivery&amp;#39;, {
  webhook: webhook,
  response: response,
  error: error,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Prefer this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;logger.warn(&amp;#39;webhook.delivery&amp;#39;, {
  &amp;#39;webhook.id&amp;#39;: webhook.id,
  &amp;#39;webhook.destination.host&amp;#39;: webhook.url.hostname,
  &amp;#39;webhook.result&amp;#39;: &amp;#39;failed&amp;#39;,
  &amp;#39;http.status_code&amp;#39;: response.status,
  &amp;#39;retry.attempt&amp;#39;: attempt,
  &amp;#39;error.name&amp;#39;: error.name,
  &amp;#39;error.code&amp;#39;: error.code,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That gives you fields you can actually query:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;message = &amp;quot;webhook.delivery&amp;quot;
webhook.result = &amp;quot;failed&amp;quot;
http.status_code &amp;gt;= 500
retry.attempt &amp;gt; 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When dealing with numeric values, it&amp;#39;s a good idea to include the unit in the attribute name. &lt;code&gt;size&lt;/code&gt; is vague. &lt;code&gt;size_bytes&lt;/code&gt; is useful. &lt;code&gt;amount&lt;/code&gt; is vague. &lt;code&gt;amount_cents&lt;/code&gt; is useful.&lt;/p&gt;
&lt;p&gt;A lot of structured logging examples out there will show timing data on the log event. If you are using a tracing provider, like Sentry, you should avoid manually timing operations in the log and implement custom spans instead.
&lt;a href=&quot;https://docs.sentry.io/concepts/key-terms/tracing/&quot;&gt;Tracing&lt;/a&gt; is the proper domain for timing operations. Because in Sentry, logs are trace-connected, you can easily correlate your logs with the spans they are associated with.&lt;/p&gt;
&lt;p&gt;A few value guidelines:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use strings, numbers, booleans, and arrays of those values.&lt;/li&gt;
&lt;li&gt;Flatten objects into the few fields you will query.&lt;/li&gt;
&lt;li&gt;Include units in numeric attribute names.&lt;/li&gt;
&lt;li&gt;Avoid logging duration or timing fields; put those on spans.&lt;/li&gt;
&lt;li&gt;Avoid logging full request, response, user, payment, or error objects.&lt;/li&gt;
&lt;li&gt;Be especially careful with arrays. Arrays of strings are often fine; arrays of objects usually are not.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;To enforce primitive values with ESLint, configure the primitive-attributes rule like this:&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Keeping consistent with a linter&lt;/h2&gt;
&lt;p&gt;Once you find a pattern, enforce it with a linter. Not only will this help you remain consistent so your logs remain useful, it also makes your AI agents smarter. You can use &lt;code&gt;/goal&lt;/code&gt; with your linter as the validation step to help automate writing and migrating logs.&lt;/p&gt;
&lt;p&gt;Start with warnings while you adopt the pattern. Turn them into errors when your team is ready to enforce it in CI.&lt;/p&gt;
&lt;p&gt;If you are using different languages for your frontend and backend, make sure you enforce the same pattern on both sides. That&amp;#39;s another situation where I would ask AI to port my ESLint rules to Flake8 for python or other linters for other languages.&lt;/p&gt;
&lt;p&gt;Linters can catch the shape of a log, but can&amp;#39;t tell whether &lt;code&gt;payment.result&lt;/code&gt; should really be &lt;code&gt;checkout.result&lt;/code&gt;, or whether the event is missing the one field you always need during an incident.&lt;/p&gt;
&lt;p&gt;For that, we can use a prompt with our AI agents to review and audit the logs for us, with our rules and guidelines in mind.
This is again where we can use the linter with the &lt;code&gt;/goal&lt;/code&gt; command to get even better results.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To review your logs with these rules, use this prompt:&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;A good log answers the next question&lt;/h2&gt;
&lt;p&gt;The point is not that every codebase needs exactly the same log schema. The point is that every codebase needs a shared idea of what a good log looks like.&lt;/p&gt;
&lt;p&gt;Start with a boring convention: stable event names, scoped keys, event attributes written with intent, and primitive values your backend can search. Then enforce the parts a linter can understand, and review the parts that need domain judgment.&lt;/p&gt;
&lt;p&gt;Good logs are not just breadcrumbs for the person who wrote the code. They are small, consistent records that help the next person answer: what happened, where did it happen, who or what was affected, and what should we look at next?&lt;/p&gt;
</content:encoded></item><item><title>MongoDB Query Tracing in .NET with Sentry + OTLP</title><link>https://blog.sentry.io/mongodb-query-tracing-dotnet-sentry/</link><guid isPermaLink="true">https://blog.sentry.io/mongodb-query-tracing-dotnet-sentry/</guid><description>Trace MongoDB queries in .NET using OpenTelemetry and Sentry&apos;s OTLP ingestion. See spans, query data, and error correlation in Sentry.</description><pubDate>Mon, 20 Jul 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If your .NET app talks to MongoDB, you almost certainly want to be able to measure DB performance so that you can effectively debug any performance issues you might run into.&lt;/p&gt;
&lt;p&gt;For that, you need to know which database command was running, how long it took, and whether this was a one-off blip or part of a broader pattern. Ideally, you also want to pivot from that trace to related errors and &lt;a href=&quot;https://sentry.io/product/session-replay/&quot;&gt;replays&lt;/a&gt; without stitching the story together by hand.&lt;/p&gt;
&lt;p&gt;This post shows how to do exactly that using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MongoDB&amp;#39;s built-in OpenTelemetry instrumentation&lt;/li&gt;
&lt;li&gt;Sentry&amp;#39;s OTLP ingestion support in the .NET SDK&lt;/li&gt;
&lt;li&gt;A small sample app that demonstrates the end-to-end flow&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once we&amp;#39;re done, you&amp;#39;ll be able to see your MongoDB spans and query data in Sentry and drill into these:&lt;/p&gt;
&lt;h2&gt;Why this approach&lt;/h2&gt;
&lt;p&gt;There were basically two ways we could have solved MongoDB instrumentation for Sentry users.&lt;/p&gt;
&lt;p&gt;One option was to build and maintain a dedicated &lt;code&gt;Sentry.MongoDB&lt;/code&gt; integration with its own &lt;code&gt;ActivityListener&lt;/code&gt; and custom mapping logic.&lt;/p&gt;
&lt;p&gt;That would work, but it also means more custom code to maintain, more translation between telemetry models, and one more special-case integration to explain.&lt;/p&gt;
&lt;p&gt;The other option, and the one we chose, was to lean into &lt;a href=&quot;https://sentry.io/solutions/opentelemetry/&quot;&gt;OpenTelemetry&lt;/a&gt; and send spans to Sentry over OTLP. MongoDB already emits OpenTelemetry-compatible &lt;code&gt;Activity&lt;/code&gt; data, and Sentry now accepts OTLP traces directly. So instead of building a bespoke bridge for each library, we can use the existing OpenTelemetry pipeline.&lt;/p&gt;
&lt;p&gt;That is exactly why &lt;code&gt;Sentry.OpenTelemetry.Exporter&lt;/code&gt; exists in &lt;code&gt;sentry-dotnet&lt;/code&gt;: it wires OpenTelemetry exporting to Sentry&amp;#39;s OTLP endpoint while keeping Sentry-specific context propagation in place.&lt;/p&gt;
&lt;h2&gt;Quick refresher: MongoDB + Activity in .NET&lt;/h2&gt;
&lt;p&gt;In .NET, OpenTelemetry tracing is built on &lt;code&gt;System.Diagnostics.Activity&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Instrumented libraries create activities when work starts and stops.&lt;/li&gt;
&lt;li&gt;OpenTelemetry SDK components listen, process, and export those activities.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As of recent MongoDB driver releases, MongoDB operations can emit this telemetry out of the box when instrumentation is enabled. That means queries, commands, and transaction-related operations can flow through your existing OpenTelemetry setup without you writing your own listener.&lt;/p&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;At a high level, you need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A .NET app using MongoDB.Driver with OpenTelemetry instrumentation support&lt;/li&gt;
&lt;li&gt;OpenTelemetry SDK packages for tracing&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Sentry.OpenTelemetry.Exporter&lt;/code&gt; in your app&lt;/li&gt;
&lt;li&gt;A Sentry DSN&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are following along with our sample from &lt;code&gt;sentry-dotnet&lt;/code&gt;, I would just use the package versions defined there since they are already validated together.&lt;/p&gt;
&lt;p&gt;Helpful references:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/getsentry/sentry-dotnet/pull/5335&quot;&gt;MongoDB sample in sentry-dotnet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.sentry.io/platforms/dotnet/tracing/instrumentation/opentelemetry-otlp/&quot;&gt;OTLP integration docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Wiring it up&lt;/h2&gt;
&lt;p&gt;The setup itself is pretty straightforward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Initialize Sentry as usual for errors.&lt;/li&gt;
&lt;li&gt;Enable OTLP mode so Sentry&amp;#39;s built-in tracing instrumentation does not duplicate spans.&lt;/li&gt;
&lt;li&gt;Configure OpenTelemetry tracing.&lt;/li&gt;
&lt;li&gt;Add MongoDB instrumentation.&lt;/li&gt;
&lt;li&gt;Export traces to Sentry using &lt;code&gt;AddSentryOtlp(...)&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A simplified shape looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;SentrySdk.Init(options =&amp;gt;
{
    options.Dsn = dsn;
    options.UseOtlp(); // &amp;lt;-- Configure Sentry to use OpenTelemetry trace information
});

using var tracerProvider = Sdk.CreateTracerProviderBuilder()
   .AddSource(MongoTelemetry.ActivitySourceName) // &amp;lt;-- Subscribe to the MongoDB driver&amp;#39;s built-in instrumentation
   .AddSentryOtlp(builder.Configuration[&amp;quot;SENTRY_DSN&amp;quot;]!)
   .Build();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key idea is simple: OpenTelemetry is responsible for producing and exporting spans, while Sentry remains responsible for crash reporting and end-to-end observability correlation and UI exploration.&lt;/p&gt;
&lt;p&gt;Note: Without &lt;code&gt;UseOtlp()&lt;/code&gt;, you can end up with duplicate spans if both OpenTelemetry and Sentry&amp;#39;s built-in tracing hooks try to generate overlapping telemetry.&lt;/p&gt;
&lt;h2&gt;Capturing useful MongoDB query data&lt;/h2&gt;
&lt;p&gt;Getting spans is table stakes. The interesting bit is the context on those spans.&lt;/p&gt;
&lt;p&gt;For MongoDB instrumentation, that includes command/query details so you can determine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which collection was involved?&lt;/li&gt;
&lt;li&gt;Which operation type is slow?&lt;/li&gt;
&lt;li&gt;Is this a repeated query pattern or a one-off spike?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The sample configures MongoDB and OpenTelemetry in a way that emits query command context and makes it visible in Sentry trace details and in the &lt;a href=&quot;https://docs.sentry.io/product/dashboards/sentry-dashboards/backend/queries/&quot;&gt;Queries&lt;/a&gt; experience.&lt;/p&gt;
&lt;h2&gt;What about PII in query data?&lt;/h2&gt;
&lt;p&gt;Query payloads can include user identifiers, emails, tokens, and other sensitive fields, which you may not be comfortable (or legally allowed) to store in Sentry.&lt;/p&gt;
&lt;p&gt;When Sentry receives your MongoDB spans via OTLP, Relay derives a parameterized copy of the query, normalizes DB attributes and scrubs all the query values (replacing them with &lt;code&gt;?&lt;/code&gt;). That parameterized string is what is used to group and display queries in the Queries module.&lt;/p&gt;
&lt;p&gt;However, Relay never removes or overwrites the original &lt;code&gt;db.query.text&lt;/code&gt;. So the raw command, with real values like &lt;code&gt;{ &amp;quot;name&amp;quot;: &amp;quot;Alice Smith&amp;quot; }&lt;/code&gt;, is retained verbatim on the stored span and shows in the span/exemplar detail view. So values are safe in the aggregate Queries view, but the raw PII is still stored and viewable per-span.&lt;/p&gt;
&lt;p&gt;There are three different options for scrubbing this data:&lt;/p&gt;
&lt;h3&gt;Don&amp;#39;t capture query text at all&lt;/h3&gt;
&lt;p&gt;This is the simplest and most reliable. Although it means you miss out on capturing some potentially very useful context.&lt;/p&gt;
&lt;h3&gt;Redact client-side via an OTEL span processor&lt;/h3&gt;
&lt;p&gt;The OTEL SDK for .NET allows you to register a class that derives from &lt;code&gt;BaseProcessor&lt;/code&gt; which can be used to redact PII via an &lt;code&gt;Activity.OnEnd&lt;/code&gt; hook. This is &lt;a href=&quot;https://github.com/getsentry/sentry-dotnet/blob/ce268174cb6a9df4a2fd1a15ea200cc3d0cc64c6/samples/Sentry.Samples.OpenTelemetry.MongoDB/RedactSensitiveMongoData.cs#L25-L38&quot;&gt;the technique we demonstrate in Sentry&amp;#39;s MongoDB sample&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;internal sealed partial class RedactSensitiveMongoData : BaseProcessor
{
    [GeneratedRegex(&amp;quot;(\&amp;quot;contributor\&amp;quot;\\s*:\\s*)\&amp;quot;[^\&amp;quot;]*\&amp;quot;&amp;quot;)]
    private static partial Regex SensitiveField();

    public override void OnEnd(Activity activity)
    {
        if (activity.GetTagItem(&amp;quot;db.query.text&amp;quot;) is string queryText)
        {
            activity.SetTag(&amp;quot;db.query.text&amp;quot;, SensitiveField()
                .Replace(queryText, &amp;quot;$1\&amp;quot;[Filtered]\&amp;quot;&amp;quot;));
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The scrubber above replaces any contributor field values with the text &lt;code&gt;[Filtered]&lt;/code&gt; so that what is sent and stored in Sentry is the redacted version that doesn&amp;#39;t contain any PII:&lt;/p&gt;
&lt;h3&gt;Server-side advanced data scrubbing&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.sentry.io/security-legal-pii/scrubbing/server-side-scrubbing/&quot;&gt;Advanced Data Scrubbing rules&lt;/a&gt; can target &lt;code&gt;db.query.text&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The default rules only catch known-sensitive patterns (passwords, tokens, card numbers): they won&amp;#39;t parameterize arbitrary field values, so for general PII you&amp;#39;d add an explicit rule/selector.&lt;/p&gt;
&lt;h2&gt;Correlating traces with Sentry errors and replays&lt;/h2&gt;
&lt;p&gt;One important detail in the OTLP integration is propagation context bridging.&lt;/p&gt;
&lt;p&gt;When traces are produced by OpenTelemetry &lt;code&gt;Activity&lt;/code&gt;, Sentry still needs to inject and read the right trace headers (&lt;code&gt;sentry-trace&lt;/code&gt; and &lt;code&gt;baggage&lt;/code&gt;) so issues, transactions, and downstream services line up correctly.&lt;/p&gt;
&lt;p&gt;That support is built into the exporter integration, so you get correlation across tracing and errors without extra wiring in your application code.&lt;/p&gt;
&lt;p&gt;In practice, that means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can jump from a slow MongoDB span to related errors or logs in the same trace.&lt;/li&gt;
&lt;li&gt;You keep consistent &lt;a href=&quot;https://sentry.io/product/tracing/&quot;&gt;distributed tracing&lt;/a&gt; context across service boundaries.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Viewing the data in Sentry&lt;/h2&gt;
&lt;p&gt;After running the sample and generating MongoDB activity, you can explore the data in a few places:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Explore -&amp;gt; Traces for end-to-end transaction timelines&lt;/li&gt;
&lt;li&gt;Span samples to inspect individual MongoDB operations&lt;/li&gt;
&lt;li&gt;Trace samples to view a waterfall showing how spans sit in the request lifecycle&lt;/li&gt;
&lt;li&gt;Queries for searching and grouping span-level behavior&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is where the OTLP route really pays off. You are not looking at a custom side-channel integration; you are looking at first-class trace data that sits naturally with the rest of your Sentry observability workflow.&lt;/p&gt;
&lt;h2&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;MongoDB already emits the right signals. OpenTelemetry already knows how to process them, and now Sentry ingests those traces natively via OTLP.&lt;/p&gt;
&lt;p&gt;Instead of maintaining another bespoke SDK integration, we can use open standards, keep the setup simple, and still preserve Sentry&amp;#39;s correlation and data-safety story.&lt;/p&gt;
&lt;p&gt;To learn more:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Try out the &lt;a href=&quot;https://github.com/getsentry/sentry-dotnet/tree/main/samples/Sentry.Samples.OpenTelemetry.MongoDB&quot;&gt;MongoDB sample in the repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Check out the &lt;a href=&quot;https://docs.sentry.io/platforms/dotnet/tracing/instrumentation/opentelemetry-otlp/&quot;&gt;OTLP integration docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As usual, questions, feedback, or ideas for where we should take this next are all very welcome, so feel free to fire up a discussion at &lt;a href=&quot;https://github.com/getsentry/sentry-dotnet/discussions&quot;&gt;sentry-dotnet discussions&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Your agent should understand what you see</title><link>https://blog.sentry.io/seer-agent-page-context/</link><guid isPermaLink="true">https://blog.sentry.io/seer-agent-page-context/</guid><description>How Sentry replaced ASCII page snapshots with structured semantic context to make Seer Agent faster, cheaper, and ready for agentic actions.</description><pubDate>Thu, 16 Jul 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Sentry&amp;#39;s &lt;a href=&quot;https://sentry.io/product/seer/agent/&quot;&gt;Seer Agent&lt;/a&gt; lets you ask any questions about your data in Sentry, dive deep into that data, and fix issues faster.&lt;/p&gt;
&lt;p&gt;One of its particularly cool abilities is letting you ask questions directly about what you&amp;#39;re looking at in Sentry. Questions like &amp;quot;What&amp;#39;s causing that latency spike?&amp;quot; or &amp;quot;What is the customer and region breakdown for this error?&amp;quot;&lt;/p&gt;
&lt;p&gt;To answer these kinds of questions, the agent must see and understand what &lt;em&gt;you&lt;/em&gt; are seeing inside Sentry at that moment. Getting that context right &lt;a href=&quot;https://blog.sentry.io/want-ai-to-be-better-at-debugging-its-all-about-context/&quot;&gt;turns out to matter a lot&lt;/a&gt;: not just for answer quality, but for cost, conversation length, and the range of things the agent can eventually do on your behalf.&lt;/p&gt;
&lt;h2&gt;The straightforward approach: ASCII snapshots&lt;/h2&gt;
&lt;p&gt;Until recently, Seer understood the current page by taking what&amp;#39;s essentially a screenshot rendered in text by scraping the DOM elements. It captures a character-by-character ASCII grid of everything visible on screen. It&amp;#39;s crude, but it works. The agent can look at that grid and infer what&amp;#39;s on the page, the same way a person could squint at a pixelated image and recognize rough shapes.&lt;/p&gt;
&lt;p&gt;But this approach has three compounding problems.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Token cost.&lt;/strong&gt; The ASCII grid is large. A single dashboard or issue feed page can consume thousands of tokens on average and p95 measurements for some pages reached 50k tokens or more. Every conversation includes at least one page snapshot, and more if the user switches views mid-chat. This cost scales with how much is on screen, not how much is actually useful to the agent.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Context rot.&lt;/strong&gt; We mainly rely on Claude Sonnet or similar models as they strike a good balance between capability and cost. They have 200k token context length by default and this is shared across the system prompt, page context, conversation history, and tool call results. As it fills up, model performance degrades: earlier turns get pushed out, the agent loses track of what was asked, and responses become less coherent.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No path to richer interactions.&lt;/strong&gt; An ASCII grid describes what&amp;#39;s visible but can&amp;#39;t express what&amp;#39;s &lt;em&gt;possible&lt;/em&gt;. There&amp;#39;s no structure to attach hints like &amp;quot;you can click &amp;#39;Edit&amp;#39; to change this widget,&amp;quot; no way to flag which elements are interactive, and no foundation for future agentic actions where the agent could directly manipulate the page on your behalf.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&amp;#39;s what it looked like before (&lt;em&gt;note: data swapped out with dummy placeholders, but you&amp;#39;ll get it, it was a big wall of everything, all at once&lt;/em&gt;):&lt;/p&gt;
&lt;h2&gt;The new approach: pages that describe themselves&lt;/h2&gt;
&lt;p&gt;Instead of ASCII screenshots, we have shifted to a structured context system that allows each page to effectively describe itself to the agent. The core shift is in &lt;em&gt;who decides what the agent sees&lt;/em&gt;. Each UI component knows what it is, what state it&amp;#39;s in, and what&amp;#39;s worth telling the agent, and it contributes that information directly.&lt;/p&gt;
&lt;p&gt;A few principles guide the design:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Semantic, not visual.&lt;/strong&gt; The agent receives meaning, &amp;quot;dashboard in edit mode, 8 widgets filtered to production,&amp;quot; rather than characters at grid coordinates. This is cheaper to send and easier to reason over.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composable and compact.&lt;/strong&gt; Each component contributes its own facts bottom-up: titles, modes, filters, key values. Decoration is omitted. New pages plug in without touching anything central.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enables more interactions.&lt;/strong&gt; The same channel that describes the page can carry hints about what&amp;#39;s interactive and, eventually, instructions flowing the other way, letting Seer act on the page rather than just read it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now it looks more like this:&lt;/p&gt;
&lt;p&gt;Under the hood, a React context provider at the root maintains a flat registry of nodes. Components opt in by wrapping themselves with a higher-order component. On mount, the wrapper registers a node and reads the nearest parent node from context to establish the hierarchy. The nesting comes for free from React&amp;#39;s component tree: a widget rendered inside a dashboard automatically becomes a child node, no manual wiring needed. Each component then uses a hook to push its own data like title, mode, active filters, key values into its node. When the agent sends a message, the flat registry is assembled into a nested tree, serialized to JSON, and converted to markdown on the backend before it enters the system prompt. Components that haven&amp;#39;t opted in yet fall back to the ASCII snapshot.&lt;/p&gt;
&lt;p&gt;With the old approach, we sent everything visible, whether it&amp;#39;s relevant or not, in a format not suited for LLMs. With the new approach, we only send useful information in a structured way that can be used to trigger targeted tool calls. The page describes itself concisely; when the agent needs more like the full rows of a table, details of a specific widget or raw data behind a chart, it fetches that on demand instead of paying for the full page context upfront.&lt;/p&gt;
&lt;h2&gt;What this looks like in practice&lt;/h2&gt;
&lt;h3&gt;More context left for the actual conversation&lt;/h3&gt;
&lt;p&gt;Structured context is now live across many of Sentry&amp;#39;s most visited pages and the token impact, measured on those pages, is significant.&lt;/p&gt;
&lt;p&gt;Before the change, page context consumed between 85-93% of the system prompt on average. With semantic context, that share drops to 50-80% across covered pages, and average token counts fall by a large amount depending on the page. On dashboards, tokens dropped from ~5,500 to ~1,300 on average for the page context section. On the issue detail pages, which is the most visited page, the reduction is even sharper: from ~2,100 tokens down to ~300. Keeping page context size as a smaller part of the prompt means the agent can have longer conversations without running into context compaction or context rot issues.&lt;/p&gt;
&lt;h3&gt;Same performance, less cost&lt;/h3&gt;
&lt;p&gt;Importantly, the efficiency gains don&amp;#39;t come at the cost of quality. Across over 14,000 runs with semantic context, we measured tool calls used per conversation and satisfaction rates measured after each conversation by using an LLM as a judge. Both satisfaction rates and average tool calls per conversation are on par with runs that rely on the prior approach. The agent makes roughly the same number of tool calls to fetch data and arrives at that point having consumed far fewer tokens to understand the page.&lt;/p&gt;
&lt;h2&gt;Where we are and what&amp;#39;s coming&lt;/h2&gt;
&lt;p&gt;The pattern is now live across 24 of Sentry&amp;#39;s most visited pages, including Dashboards, Issues, Explore, Traces, Logs, Replays, Performance, and more. Each page was a self-contained addition with no central changes required, which is exactly how the composable architecture was meant to pay off.&lt;/p&gt;
&lt;p&gt;The first agent action built on top of this infrastructure is also ready: Seer can now &lt;a href=&quot;https://blog.sentry.io/dashboard-updates/&quot;&gt;create and update dashboards on your behalf&lt;/a&gt;. You can describe what you want to see (e.g. &amp;quot;a dashboard showing p95 latency by endpoint over the last 7 days alongside error rate&amp;quot;) and the agent produces a fully configured dashboard with the right layout, queries, and widget types. Other teams are working on providing the agent semantic context and exploring options to have the agent interact with their page and take actions in the UI; watch for more built on this within Sentry in the months ahead!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Thanks to Jeremy Stanley and Jonas Badalic for guiding and helping throughout this effort.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>When and what should I be logging?</title><link>https://blog.sentry.io/logging-best-practices/</link><guid isPermaLink="true">https://blog.sentry.io/logging-best-practices/</guid><description>Learn logging best practices: what to log, how to structure messages, and what to avoid. Practical tips with Sentry&apos;s structured logging.</description><pubDate>Thu, 09 Jul 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;This is a follow-up to Sergiy&amp;#39;s post &lt;a href=&quot;https://blog.sentry.io/errors-traces-logs-metrics-when-to-reach-for-what/&quot;&gt;Errors, traces, logs, metrics: when to reach for what&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Modern observability platforms, like Sentry, give developers a lot of choice. For a given problem, should you use traces, profiles, metrics, &lt;a href=&quot;https://sentry.io/product/logs/&quot;&gt;logs&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;If you take away one thing from this post, I hope it&amp;#39;s this: &lt;strong&gt;when in doubt, start by adding a few targeted log lines.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Logs are straightforward to add to your application and they&amp;#39;re a fast way to start collecting real information about how your software is running in production. When writing a new feature, I try to add enough logs that it&amp;#39;s debuggable without a deployment.&lt;/p&gt;
&lt;p&gt;It&amp;#39;s okay for logs to be temporary instrumentation. Add them when investigating a problem or validating a feature, remove them when they&amp;#39;re no longer useful.&lt;/p&gt;
&lt;p&gt;Here are some best practices for using logs to make your application easier to debug and reason about.&lt;/p&gt;
&lt;h2&gt;Great things to consider logging&lt;/h2&gt;
&lt;h3&gt;Important runtime decisions made by your application&lt;/h3&gt;
&lt;p&gt;Different users often see different flows. When you&amp;#39;re debugging unexpected behavior, you want to know all the different decisions that determined how a request was served.&lt;/p&gt;
&lt;p&gt;Some examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A user has a feature flag enabled that exposes an experimental version of a page.&lt;/li&gt;
&lt;li&gt;Mobile users are redirected to a different experience.&lt;/li&gt;
&lt;li&gt;Paid and free users receive different functionality.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When your application chooses between multiple code paths, consider logging both why the decision was made and what behavior resulted:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Runtime decision: this user has a feature flag enabled
// that exposes an experimental version of the feed.
Sentry.logger.info(&amp;quot;Check Feature Flags&amp;quot;, {
  feature_flag: &amp;quot;fiesta_mode&amp;quot;,
  feed_experience: &amp;quot;party mode&amp;quot;,
  &amp;quot;user.id&amp;quot;: user_uuid
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These logs make it easier to understand why two users may have experienced the application differently and to reproduce bugs that only affect specific cohorts.&lt;/p&gt;
&lt;h3&gt;Whether a feature or algorithm is behaving as expected&lt;/h3&gt;
&lt;p&gt;Logs are useful when a feature performs multiple steps. By recording intermediate outcomes, you can understand where a process is breaking down and why.&lt;/p&gt;
&lt;p&gt;Here&amp;#39;s a real-world example. My site, &lt;a href=&quot;https://www.allaboard.dev/&quot;&gt;allaboard.dev&lt;/a&gt;, allows users to import a climbing log-book from an external service. Logging the outcome of the import process helps me verify that the source data is being parsed correctly and identify where and if it fails.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Stage 1 outcome: the export was authenticated and parsed.
// Record how much work we&amp;#39;re about to do — if a user reports
// a bad import, the first question is &amp;quot;how many entries did
// we even receive?&amp;quot;
Sentry.logger.info(&amp;quot;Third-party import started&amp;quot;, {
  &amp;quot;import.source&amp;quot;: &amp;quot;aurora&amp;quot;,
  &amp;quot;import.entries_received&amp;quot;: body.ascents.length,
});

// Algorithm runs here, populating skipDetails.

// Final stage outcome: a flat, queryable breakdown of how the
// run resolved. Each skip reason is its own scalar field so you
// can chart, say, a spike in `import.skipped.unknown_grade`
// (a Font→V-scale conversion gap) on its own.
Sentry.logger.info(&amp;quot;Third-party import finished&amp;quot;, {
  &amp;quot;import.source&amp;quot;: &amp;quot;aurora&amp;quot;,
  &amp;quot;import.entries_received&amp;quot;: body.ascents.length,
  &amp;quot;import.imported&amp;quot;: imported,
  &amp;quot;import.climbs_created&amp;quot;: climbsCreated,
  &amp;quot;import.skipped&amp;quot;: skipped,
  &amp;quot;import.skipped.missing_name&amp;quot;: skipDetails.missingName,
  &amp;quot;import.skipped.unknown_grade&amp;quot;: skipDetails.unknownGrade,
  &amp;quot;import.skipped.invalid_angle&amp;quot;: skipDetails.invalidAngle,
  &amp;quot;import.skipped.already_imported&amp;quot;: skipDetails.alreadyImported,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Audit and access events (creates, updates, deletes, access, permissions)&lt;/h3&gt;
&lt;p&gt;Audit logs help answer questions like &amp;quot;Who changed this?&amp;quot;, &amp;quot;When did it happen?&amp;quot;, and &amp;quot;Was this action expected?&amp;quot;&lt;/p&gt;
&lt;p&gt;These types of logs can be great for root causing support cases.&lt;/p&gt;
&lt;p&gt;Perhaps a user writes in and asks &lt;em&gt;&amp;quot;where the heck did my team&amp;#39;s weekly dashboard go?&amp;quot;&lt;/em&gt; Because your application logs mutating operations (&lt;em&gt;creates, deletes, updates&lt;/em&gt;), you&amp;#39;re able to see that another member of their team accidentally deleted the dashboard a few days earlier. You restore the dashboard, let the user know exactly what happened, and have the peace of mind that your application isn&amp;#39;t randomly deleting things.&lt;/p&gt;
&lt;p&gt;Logging access and permissions can also be a requirement for some standards, like HIPAA.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Audit logs are only one part of meeting compliance requirements. See &lt;a href=&quot;https://sentry.io/trust/privacy/&quot;&gt;Sentry and Your Data&lt;/a&gt; to learn about the privacy and security controls Sentry provides. If you have complex compliance or privacy requirements, &lt;a href=&quot;https://sentry.io/contact/enterprise/&quot;&gt;talk to us&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Context surrounding errors and failures&lt;/h3&gt;
&lt;p&gt;For exceptions, you&amp;#39;ll often be better off using Sentry&amp;#39;s &lt;a href=&quot;https://docs.sentry.io/platforms/javascript/usage/&quot;&gt;Capture Error&lt;/a&gt; functionality rather than adding a log line. This gives you the benefit of &lt;a href=&quot;https://docs.sentry.io/concepts/data-management/event-grouping/&quot;&gt;Issue Grouping&lt;/a&gt;, triage workflows, &lt;a href=&quot;https://sentry.io/product/seer/autofix/&quot;&gt;Autofix&lt;/a&gt;, and other issue-focused features.&lt;/p&gt;
&lt;p&gt;However, not every failure should immediately become an error in Sentry. For example, you may rely on a flaky upstream API and allow certain HTTP status codes to be retried &lt;em&gt;N&lt;/em&gt; times. When you hit &lt;em&gt;N&lt;/em&gt;, you want an error to be raised with Sentry. For attempts before &lt;em&gt;N&lt;/em&gt;, a log line explaining why the retry loop is happening can be useful during debugging.&lt;/p&gt;
&lt;p&gt;What context might you want to log?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Retry count or attempt number.&lt;/li&gt;
&lt;li&gt;Status codes, error codes, or other non-sensitive details returned by upstream services.&lt;/li&gt;
&lt;li&gt;Non-sensitive request/response attributes that help explain the failure.&lt;/li&gt;
&lt;li&gt;Runtime state relevant to the failure, such as feature flags, configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that we&amp;#39;ve given some recommendations on what to log, let&amp;#39;s look at how to structure your log messages.&lt;/p&gt;
&lt;h2&gt;How to write your log messages&lt;/h2&gt;
&lt;h3&gt;Use structured log messages&lt;/h3&gt;
&lt;p&gt;Rather than plain-text logs like &lt;code&gt;&amp;quot;DID I GET HERE&amp;quot;&lt;/code&gt;, prefer structured logs that capture information as consistent key/value pairs.&lt;/p&gt;
&lt;p&gt;Structured logs benefit both humans and machines. Consistent fields such as &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;request_id&lt;/code&gt;, &lt;code&gt;feature_flag&lt;/code&gt;, or &lt;code&gt;action&lt;/code&gt; make debugging easier, and they can be used by logging platforms for search, visualizations, and &lt;a href=&quot;https://docs.sentry.io/product/monitors-and-alerts/alerts/&quot;&gt;alerting&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A good log message typically answers three questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Who&lt;/strong&gt; performed the action (for example, the authenticated user).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What&lt;/strong&gt; happened (a human-readable message and supporting metadata).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;When&lt;/strong&gt; it happened (typically added automatically by the logging system).&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; For including context about the currently authenticated user, Sentry provides the &lt;a href=&quot;https://docs.sentry.io/platforms/javascript/configuration/apis/#setUser&quot;&gt;setUser&lt;/a&gt; method.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Add context as a request evolves&lt;/h3&gt;
&lt;p&gt;Logs should accumulate context as a request moves through your application. Emit that context alongside event-specific metadata in your log messages.&lt;/p&gt;
&lt;p&gt;For example, logs emitted before authentication won&amp;#39;t contain user information. Later in the request lifecycle, after authentication, user-specific details should be included alongside context specific to the event being logged.&lt;/p&gt;
&lt;p&gt;One particularly valuable piece of context is the Trace ID. This allows you to connect a log entry back to a &lt;a href=&quot;https://docs.sentry.io/platforms/javascript/tracing/distributed-tracing/&quot;&gt;distributed trace&lt;/a&gt;, making it easier to understand the sequence of events that led to the log message being emitted.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Good news: Sentry&amp;#39;s logs are trace-connected out of the box, so trace context is automatically included when available.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Choose the appropriate log level&lt;/h3&gt;
&lt;p&gt;Using appropriate log levels is an additional way to convey meaning in your log messages.&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;debug&lt;/code&gt; for detailed diagnostic information that is useful during development, or certain investigations. Debug logs are often disabled in production and enabled temporarily when troubleshooting a problem.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Sentry&amp;#39;s &lt;a href=&quot;https://docs.sentry.io/platforms/java/logs/#beforesendlog&quot;&gt;&lt;code&gt;beforeSendLog&lt;/code&gt;&lt;/a&gt; method can be used to filter out debug level logs by looking at the &lt;code&gt;level&lt;/code&gt; property. &lt;code&gt;beforeSendLog(log) {return log.level !== &amp;#39;debug&amp;#39;}&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Use &lt;code&gt;info&lt;/code&gt; for normal application events. Runtime decisions being made, algorithm behaviour, audit logs, these are all candidates for &lt;code&gt;info&lt;/code&gt;-level logs.&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;warn&lt;/code&gt; for recoverable events that may require attention. A good example might be when an API call to an external service reaches a threshold for latency.&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;error&lt;/code&gt; for unexpected failures that are handled gracefully. If a failure results in an exception, prefer Sentry&amp;#39;s &lt;a href=&quot;https://docs.sentry.io/platforms/javascript/usage/&quot;&gt;Capture Error&lt;/a&gt; over a duplicate error log.&lt;/p&gt;
&lt;h2&gt;What &lt;em&gt;not&lt;/em&gt; to log&lt;/h2&gt;
&lt;h3&gt;Every function call, or line of code, in your application&lt;/h3&gt;
&lt;p&gt;Instrumenting every function call is usually better handled by profiling and tracing. Did you know Sentry has &lt;a href=&quot;https://sentry.io/product/profiling/&quot;&gt;profiling&lt;/a&gt; and &lt;a href=&quot;https://sentry.io/product/tracing/&quot;&gt;tracing&lt;/a&gt;! (&lt;a href=&quot;https://blog.sentry.io/authors/rahul-chhabria/&quot;&gt;&lt;em&gt;Rahul&lt;/em&gt;&lt;/a&gt; &lt;em&gt;made me say this&lt;/em&gt;).&lt;/p&gt;
&lt;h3&gt;PII and other sensitive information&lt;/h3&gt;
&lt;p&gt;Whenever logging a piece of information, ask yourself: &amp;quot;What would the impact be if the wrong person gained access to this information?&amp;quot;&lt;/p&gt;
&lt;p&gt;Some guidelines:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prefer opaque user IDs over email addresses or full names when possible.&lt;/li&gt;
&lt;li&gt;Passwords, access tokens, API keys, and similar secrets should never appear in logs. Store them only in systems designed for secret storage.&lt;/li&gt;
&lt;li&gt;Other types of personal information may also be regulated depending on jurisdiction, including age, gender, and postal code.&lt;/li&gt;
&lt;li&gt;Be aware of local and international laws and standards, such as PCI, GDPR, CCPA, and HIPAA, which provide guidance on what should and shouldn&amp;#39;t be logged, retained, or exposed.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Sentry has &lt;a href=&quot;https://docs.sentry.io/security-legal-pii/scrubbing/server-side-scrubbing/&quot;&gt;Server-Side Data Scrubbing&lt;/a&gt;. If you use structured logging, this can help protect against some common PII and password pitfalls. It is configurable, so you can include additional fields applicable to your application. You can also use &lt;a href=&quot;https://docs.sentry.io/platforms/javascript/logs/#filter-logs&quot;&gt;&lt;code&gt;beforeSendLog&lt;/code&gt;&lt;/a&gt; to perform client-side filtering of sensitive information.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;TL;DR: Be intentional about what you log. Log the minimum information necessary to debug and operate your application, and understand the requirements that apply to your industry, your country, and your customers&amp;#39; countries.&lt;/p&gt;
&lt;h3&gt;Large blobs of data (without a specific purpose)&lt;/h3&gt;
&lt;p&gt;There are legitimate reasons to log large, unstructured blobs of data:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Seeing a full LLM prompt and response may help you understand whether your product is behaving as expected.&lt;/li&gt;
&lt;li&gt;Logging a webhook body may help you debug issues with an external integration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, logging this type of data has both costs and risks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Users may include personal or sensitive information in an LLM prompt.&lt;/li&gt;
&lt;li&gt;Entire HTTP requests and responses may contain access tokens, secrets, or other sensitive data.&lt;/li&gt;
&lt;li&gt;Many logging products, Sentry included, charge based on volume. Ask yourself whether you&amp;#39;ll actually use the information you&amp;#39;re storing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;For AI assistants, you may also be better off using a purpose-built solution, such as Sentry&amp;#39;s &lt;a href=&quot;https://docs.sentry.io/product/agents/conversations/&quot;&gt;Conversations feature&lt;/a&gt;, rather than logging entire conversations.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As with the PII discussion, the important takeaway is to be intentional about what you log. Consider the cost and risk associated with the data, and when possible, prefer logging the specific fields you need rather than an entire request, response, or document.&lt;/p&gt;
&lt;h2&gt;Applying these suggestions to allaboard.dev&lt;/h2&gt;
&lt;p&gt;Here are some examples of the logs I&amp;#39;ve added to my aforementioned side project &lt;a href=&quot;https://github.com/bcoe/allaboard.dev&quot;&gt;bcoe/allaboard.dev&lt;/a&gt;, following the advice from this article:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/bcoe/allaboard.dev/commit/32499307cd4170e87c4f96aca741da4c9c8f94a4&quot;&gt;Runtime decisions, such as feature flags&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/bcoe/allaboard.dev/commit/41a17c7441c2bfbf25d757fab649b16a68393dab&quot;&gt;Steps and outcome from an algorithm&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/bcoe/allaboard.dev/commit/091828e9f43c7f56fa44b68312e9a7389f7beb68&quot;&gt;Audit and access events&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/bcoe/allaboard.dev/commit/9c9788043d6f5ada23fbe33e35c0e1f1aea75d21&quot;&gt;Context for non-critical errors&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;#39;m now much more confident that, when something goes wrong, I have enough context to debug the problem without deploying a new version of the website.&lt;/p&gt;
&lt;h2&gt;What&amp;#39;s next?&lt;/h2&gt;
&lt;p&gt;I hope this article has given you some inspiration about the &lt;em&gt;what&lt;/em&gt; and &lt;em&gt;how&lt;/em&gt; of logging, to help speed up debugging next time something tricky happens in production.&lt;/p&gt;
&lt;p&gt;If you&amp;#39;d like some help getting started, try &lt;a href=&quot;https://github.com/getsentry/sentry-for-ai/blob/main/skills-legacy/sentry-instrument-logging/SKILL.md&quot;&gt;getsentry/sentry-for-ai&lt;/a&gt;. It can analyze your codebase and suggest an initial set of high-value structured log messages to add.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Try it out&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;First, run this command to install the Sentry plugin&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx @sentry/ai install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Then paste this in your agent of choice&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Please instrument Sentry logging. Include some examples following best practices.
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;a href=&quot;https://github.com/getsentry/sentry-for-ai&quot;&gt;getsentry/sentry-for-ai&lt;/a&gt; is being iterated on rapidly and the paths to some skills may change. Keep an eye on the repository for up-to-date install instructions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Have any feedback, or just want to talk more about logging? Join us on &lt;a href=&quot;https://discord.com/invite/sentry&quot;&gt;Discord&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Any Apple update can break our app. Here&apos;s how we find out first.</title><link>https://blog.sentry.io/apple-update-breaks-app/</link><guid isPermaLink="true">https://blog.sentry.io/apple-update-breaks-app/</guid><description>How the Usage app uses Sentry to catch crashes, silent errors, and build regressions before 1.6 million users notice.</description><pubDate>Thu, 02 Jul 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;This is a guest post by Dan Mindru and Oleh Stasula. Dan is an indie maker, enterprise consultant, and co-host of the &lt;a href=&quot;https://morningmakershow.com/&quot;&gt;Morning Maker Show&lt;/a&gt;. Oleh is a full-time indie developer and founder of &lt;a href=&quot;https://winwinkit.com&quot;&gt;WinWinKit&lt;/a&gt; and &lt;a href=&quot;https://usage.pro&quot;&gt;Usage&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It feels like with every release, we are walking a tightrope. We need to keep our app lightweight, stable, and performant, all the while depending on APIs that can shift at any moment (without warning, too!).&lt;/p&gt;
&lt;p&gt;After 1.6 million downloads, we have a responsibility to our users to keep quality high. We are proud to have kept our rating to 4.7 stars on the App Store, even though we&amp;#39;re a tiny team. So how do we do it?&lt;/p&gt;
&lt;p&gt;The short answer: a safety net, built with Sentry. This post lays out how we use it at scale, from fixing crashes before users notice, to catching build issues, to getting user feedback that goes beyond the stack trace.&lt;/p&gt;
&lt;h2&gt;Setting the stage&lt;/h2&gt;
&lt;p&gt;The application in question is called &lt;a href=&quot;https://usage.pro/&quot;&gt;Usage&lt;/a&gt;, a system activity monitor that works across iPhone, iPad, and Mac. It tracks how your device uses its resources: CPU, memory, network, disk, battery, graphics. This plus the rich historical data makes it popular to power users that like to keep a close eye on their device.&lt;/p&gt;
&lt;p&gt;This comes with a few challenges.
Usage&amp;#39;s entire job is to watch your device work without becoming part of the workload. That makes for a strange engineering constraint: every feature we add, every dependency we pull in, every timer we schedule competes with the very thing we promise our users. &lt;strong&gt;Nobody wants a system monitor that shows up in its own charts&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;And that&amp;#39;s not even the half of it.&lt;/em&gt;
The ground under us isn&amp;#39;t stable either. To read some of the data we need, we lean on undocumented APIs in places where Apple doesn&amp;#39;t offer a public alternative. That means any OS update can quietly change a data structure under us, on any device, at any time. We don&amp;#39;t get a deprecation notice. So we need to stay on top of it at all times.&lt;/p&gt;
&lt;p&gt;Usage is also not a single process. The app is built as a small constellation of cooperating components: the main and menu bar apps, the background service, and the widgets. Each has its own lifecycle and its own failure modes. To keep that visible, each component is a separate Sentry project, all grouped under the same workspace. That way crashes and errors stay attributable to the exact process they came from, while we still get a single place to look at the health of the whole system.&lt;/p&gt;
&lt;p&gt;Keeping all of this smooth and performant is no small feat, and we lean on a good chunk of Sentry&amp;#39;s toolbox to do it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://sentry.io/solutions/mobile-developers/&quot;&gt;Crash Monitoring&lt;/a&gt;&lt;/strong&gt; is the baseline that everything else builds on.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://sentry.io/product/error-monitoring/&quot;&gt;Error Monitoring&lt;/a&gt;&lt;/strong&gt; catches the quiet failures, like an OS update changing a data structure we depend on.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://sentry.io/product/size-analysis/&quot;&gt;Size Analysis&lt;/a&gt;&lt;/strong&gt; diffs every build, so a broken release can&amp;#39;t sneak past us twice.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://sentry.io/product/metrics/&quot;&gt;Metrics&lt;/a&gt;&lt;/strong&gt; is how we plan to prove that our optimization work actually works.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://sentry.io/for/user-feedback/&quot;&gt;User Feedback&lt;/a&gt;&lt;/strong&gt; gives users a direct line to us from inside the app.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&amp;#39;s how we use each of them.&lt;/p&gt;
&lt;h2&gt;Crash Monitoring&lt;/h2&gt;
&lt;p&gt;Crash monitoring is the baseline. Every app needs it, and most of what makes Sentry valuable to us later in this article rests on having the &lt;a href=&quot;https://docs.sentry.io/platforms/apple/&quot;&gt;Sentry SDK&lt;/a&gt; initialized early in the app&amp;#39;s launch.&lt;/p&gt;
&lt;p&gt;In each of our targets, Sentry initialization looks pretty standard:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;

SentrySDK.start { options in
   options.dsn = &amp;quot;___PUBLIC_DSN___&amp;quot;

   // deviceId is a UUID assigned at the first launch and used for:
   // - cross-device sync when enabled
   // - troubleshooting / debugging
   SentrySDK.setUser(User(userId: deviceId))
   SentrySDK.configureScope { scope in
       scope.setTag(value: deviceId, key: &amp;quot;device_id&amp;quot;)
   }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From this point on, any crash in the app is captured, whether it&amp;#39;s a hard signal, an uncaught Swift error, or an &lt;code&gt;NSException&lt;/code&gt;. Each one comes with a symbolicated stack trace, the breadcrumbs that led up to it, and the exact release it happened in.&lt;/p&gt;
&lt;p&gt;What we actually get from that is a clear picture of stability over time. It&amp;#39;s the foundation everything else in this article builds on.&lt;/p&gt;
&lt;h2&gt;Error Monitoring&lt;/h2&gt;
&lt;p&gt;With crashes out of the way, let&amp;#39;s talk about all subtle errors. A crash at least announces itself. What worries us more is the failure that doesn&amp;#39;t: the app keeps running, but somewhere a reading has quietly gone wrong. This usually results in a wrong metric, and a wrong metric is an unhappy user.&lt;/p&gt;
&lt;p&gt;For Usage, it&amp;#39;s critical to catch these errors early and react quickly. As we mentioned earlier, many parts of the app read system data through undocumented sources and APIs. We don&amp;#39;t always know in advance what shape a given reading will take on a given device, OS version, and hardware generation.&lt;/p&gt;
&lt;p&gt;Comprehensive error logging is what lets us live with that uncertainty. Every time the app reads a value it doesn&amp;#39;t recognize, we capture it as a &lt;a href=&quot;https://docs.sentry.io/platforms/apple/usage/&quot;&gt;non-fatal error&lt;/a&gt; in Sentry. That covers a missing field, a structure of a type we don&amp;#39;t support yet, or a number outside the expected range. This is also where Sentry helps us with privacy. An event only carries the context we choose to attach. We send the &lt;em&gt;shape&lt;/em&gt; of the unexpected reading, like the type and fields of a graphics card response. We never send the values, and we never send anything about the user. And if something slips through anyway, &lt;a href=&quot;https://docs.sentry.io/platforms/apple/configuration/filtering/&quot;&gt;&lt;code&gt;beforeSend&lt;/code&gt;&lt;/a&gt; gives us one last checkpoint to scrub each event before it leaves the device.&lt;/p&gt;
&lt;p&gt;One concrete example we hit most often: graphics card readings. The data structure Apple returns for GPU stats has shifted subtly between macOS versions and between hardware generations. When a new Mac shows up with a slightly different shape, we&amp;#39;d be flying blind. Instead, we get a Sentry event the same day with the unexpected structure recorded.&lt;/p&gt;
&lt;p&gt;That early warning means support for the new shape usually ships in the next release. By the time most users get that hardware, &lt;strong&gt;the fix is already out&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;When &lt;code&gt;Error&lt;/code&gt; is not enough&lt;/h3&gt;
&lt;p&gt;A stack trace alone is rarely enough to figure out what went wrong with a parsing or decoding error. We almost always want to see the &lt;em&gt;thing that failed&lt;/em&gt; alongside the error itself.&lt;/p&gt;
&lt;p&gt;Swift&amp;#39;s &lt;code&gt;Error&lt;/code&gt; protocol doesn&amp;#39;t carry an arbitrary payload by default, so we add one. A small wrapper struct and a single extension method are enough:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ErrorWithData: Error {
    let error: Error
    let data: Any
}

extension Error {

   func with(data optionalData: Any?) -&amp;gt; Error {
        if let data = optionalData {
            return ErrorWithData(
                error: self,
                data: data
            )
        }
        return self
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;with(data:)&lt;/code&gt; method is the call-site sugar: it lets us tack context onto any error without rewriting our error types, and it gracefully no-ops when there&amp;#39;s nothing to attach. A typical use looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;throw GraphicsHelperFetcherError.displayMissingProperty(&amp;quot;sppci_model&amp;quot;).with(data: rawResponse)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then at the capture site, we unwrap the data and put it on the Sentry scope as an &lt;a href=&quot;https://docs.sentry.io/platforms/apple/enriching-events/context/&quot;&gt;extra&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;SentrySDK.capture(error: actualError) { scope in
    ...
    if let data = (error as? ErrorWithData)?.data {
       scope.setExtra(value: data, key: &amp;quot;data&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In Sentry, the data ends up as a labeled field on the event, right next to the stack trace. For the kinds of errors we deal with, unrecognized system readings and unexpected field shapes, that one field is often what makes the difference.&lt;/p&gt;
&lt;p&gt;Here&amp;#39;s that pattern in action.
The screenshots below show a real Sentry event from the macOS app: a parsing error on a graphics reading where one of the display objects came back without a property we expected. The stack trace tells us where it failed, but the &lt;code&gt;data&lt;/code&gt; field tells us &lt;em&gt;why&lt;/em&gt;: the raw structure is right there, missing the field, on a GPU and macOS combination we hadn&amp;#39;t seen before. That&amp;#39;s our signal to update our handling of display objects in graphics data, and we can usually ship the fix in the next release.&lt;/p&gt;
&lt;h2&gt;Size Analysis&lt;/h2&gt;
&lt;p&gt;Crash and error monitoring watch the app while it runs. They can&amp;#39;t catch a build that was broken before anyone ever launched it. We learned that one the hard way.&lt;/p&gt;
&lt;p&gt;A while back, a refactor accidentally removed one of the app&amp;#39;s localizations. Somehow the diff didn&amp;#39;t go through our usual review process, and a build with the broken refactor was uploaded and selected for release. Once it hit production, the effect was immediate and not subtle: active users in the affected locales fell off a cliff before we understood why.&lt;/p&gt;
&lt;p&gt;While rare, these things can happen on any team. We needed a second line of defense beyond reviewing diffs.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.sentry.io/product/size-analysis/&quot;&gt;Sentry&amp;#39;s Size Analysis&lt;/a&gt; turned out to be exactly that. It runs on every release build, attaches a size report to the same &lt;code&gt;version+build&lt;/code&gt; release tag the rest of Sentry already uses, and diffs the new build against the previous one. If a localization file, an asset, or an entire bundle resource disappears, the diff makes it impossible to miss. Same goes for the inverse: a release that mysteriously grew has its cause sitting right there in the breakdown.&lt;/p&gt;
&lt;p&gt;Even though Size Analysis is (probably) meant for only keeping app size in check (and we use it for that too!), what sold us is using it as a regression gate. We can&amp;#39;t guarantee that a human or automated process will catch these issues in a review, and size analysis works well as an additional sanity check.&lt;/p&gt;
&lt;p&gt;On top of the size reports, we&amp;#39;ve set up a Sentry monitor that watches the delta between releases and alerts us whenever a build comes in noticeably heavier or lighter than the previous one. The &amp;quot;lighter&amp;quot; direction is what makes the localization incident obvious; the &amp;quot;heavier&amp;quot; direction catches bloat creeping in. Either way, now we can rest assured that we&amp;#39;ll know about these issues before they become a problem.&lt;/p&gt;
&lt;h2&gt;Metrics&lt;/h2&gt;
&lt;p&gt;Everything above is about catching what goes wrong. The last piece is proving what goes right.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.sentry.io/product/metrics/&quot;&gt;Metrics&lt;/a&gt; is a newer Sentry feature that we&amp;#39;re in the middle of adopting. For an app like Usage, performance and resource footprint aren&amp;#39;t just a nice-to-have; they&amp;#39;re the product. So being able to emit our own counters and gauges from anywhere in the codebase, and see them sitting next to crashes and errors, is a big deal.&lt;/p&gt;
&lt;p&gt;We&amp;#39;re instrumenting the things we already care about, like sampling latency, background work duration, and sync intervals, so Metrics can confirm that our optimization work is actually moving those numbers.&lt;/p&gt;
&lt;p&gt;So far the results are promising. We&amp;#39;ll write a proper deep dive once we&amp;#39;ve used it extensively.&lt;/p&gt;
&lt;h2&gt;User Feedback&lt;/h2&gt;
&lt;p&gt;Not everything users do shows up as a crash or error. Sometimes the app is technically healthy and someone is still staring at a chart that doesn&amp;#39;t look right. For those cases, we&amp;#39;d rather they tell us directly.&lt;/p&gt;
&lt;p&gt;We first tried Sentry&amp;#39;s &lt;a href=&quot;https://docs.sentry.io/product/user-feedback/&quot;&gt;User Feedback&lt;/a&gt; in our other product, &lt;a href=&quot;https://winwinkit.com/&quot;&gt;WinWinKit&lt;/a&gt;, and once we saw the value we knew we had to immediately add it to Usage too.&lt;/p&gt;
&lt;p&gt;It became one of the bigger drivers of faster iteration and stronger user engagement.
It&amp;#39;s an easy one for us to recommend. With a few lines of code you get a small in-app form that lets users send feedback without leaving the app. The feedback lands in the same workspace as our crashes and errors, tagged with the same release.&lt;/p&gt;
&lt;p&gt;If you&amp;#39;re on the fence about it, we can definitely recommend it. Every report makes the next build a little better than it would have been.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;We&amp;#39;re still walking the tightrope, but these days we actually enjoy the view. Apple will ship another update, some struct will grow a new shape, and a Mac we&amp;#39;ve never touched will send us bytes we&amp;#39;ve never seen.
Bring it on!&lt;/p&gt;
&lt;p&gt;If you&amp;#39;re building something that depends on a platform you don&amp;#39;t control (and honestly, who isn&amp;#39;t?), our advice is simple: have as many safety nets as you can. Your users will thank you for it!&lt;/p&gt;
</content:encoded></item><item><title>Reading the agent traces is how you make the call your eval can&apos;t</title><link>https://blog.sentry.io/spot-checking-ai-agents/</link><guid isPermaLink="true">https://blog.sentry.io/spot-checking-ai-agents/</guid><description>I gave the free tier a cheaper model and it invented conference speakers who don&apos;t exist. What that taught me about model tradeoffs, evals, and reading agent traces.</description><pubDate>Wed, 01 Jul 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Remember being excited (or dreading, depending on the stage of your career and the company you worked at) about writing unit tests? Or sweating all the details in your end-to-end and integration tests you were sure covered all the use cases your users would hit?&lt;/p&gt;
&lt;p&gt;These days a lot of UIs are slowly being replaced by a single input field and an agent that promises to deliver the same value a UI would, but with the elegance and pun-ness of a “Jarvis”.&lt;/p&gt;
&lt;p&gt;We craft their &lt;code&gt;SOUL.md&lt;/code&gt; and their &lt;code&gt;MEMORY.md&lt;/code&gt; and the system prompt. We pretend we know what we’re doing setting up evals with prompts we know are not how our users will interact with the agent, but we set the threshold and the confidence score comes back satisfactory and we approve and deploy. Job’s done, right?&lt;/p&gt;
&lt;p&gt;Not quite.&lt;/p&gt;
&lt;p&gt;Sentry is attending AI Engineer World&amp;#39;s Fair this week and I decided to build a little &lt;a href=&quot;https://aie-wf.sentry.dev/&quot;&gt;schedule builder&lt;/a&gt; with an agent to help people put together their itineraries. (Shout out to &lt;a href=&quot;https://x.com/swyx&quot;&gt;Swyx&lt;/a&gt; for providing the data and even the embeddings for all the speakers, talks and tracks.)&lt;/p&gt;
&lt;p&gt;I wanted to make sure that this is somewhat scalable, and thanks to the AI SDK and Vercel’s AI Gateway, it was pretty easy to configure the agent to use some open weight, incredibly cheap models for anonymous visitors, and more beefy, SOTA-ish models for people that signed up.&lt;/p&gt;
&lt;p&gt;Surely, with tool calling, and a great system prompt, there is no way even the small-but-mighty model would hallucinate, right? Right???&lt;/p&gt;
&lt;h2&gt;“Who are some famous speakers?”&lt;/h2&gt;
&lt;p&gt;The assistant has a router that reads each question and sends it down one of two paths: an &amp;quot;info&amp;quot; path and a &amp;quot;search&amp;quot; path. My speakers question went to the info path, which has exactly one data tool, &lt;code&gt;getTracks&lt;/code&gt;. Here is everything &lt;code&gt;getTracks&lt;/code&gt; returns:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[
  {
    &amp;quot;id&amp;quot;: &amp;quot;agentic-commerce&amp;quot;,
    &amp;quot;name&amp;quot;: &amp;quot;Agentic Commerce&amp;quot;,
    &amp;quot;color&amp;quot;: &amp;quot;#22c55e&amp;quot;,
    &amp;quot;description&amp;quot;: &amp;quot;Agentic Commerce sessions at AI Engineer World&amp;#39;s Fair 2026 in San Francisco.&amp;quot;
  }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the confident fashion, that most coding agents will proceed to tell you the most frustrating half-fact / blatant lie, it told me that Sam Altman and Jensen Huang will be on the main stage. One of the names it gave me, &amp;quot;Chris Hagen, Founder of Stability AI,&amp;quot; isn&amp;#39;t even a real person.&lt;/p&gt;
&lt;p&gt;When I asked how it got Sam Altman, it doubled down:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I didn&amp;#39;t &amp;quot;guess&amp;quot; Sam Altman. I pulled him straight from the official conference data that the API provides. I called the &lt;code&gt;getTracks&lt;/code&gt; function, which returns every track that&amp;#39;s been scheduled for the fair, along with the speakers listed for each session.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;getTracks&lt;/code&gt; does not return speakers. It invented the data, then invented a source for the data.&lt;/p&gt;
&lt;p&gt;The trace lists &amp;quot;Used Tools: getTracks,&amp;quot; which is the part that makes this sneaky. A tool ran, so the answer reads as grounded. You only catch it by opening that tool call and seeing tracks where speakers should be.&lt;/p&gt;
&lt;h2&gt;Hindsight is always 20/20&lt;/h2&gt;
&lt;p&gt;An eval probably would have caught this one. But would I have thought to write it in the first place? The agent named people the app&amp;#39;s &lt;code&gt;speakers&lt;/code&gt; table has never heard of. A groundedness check, assert that every person the answer names shows up in the tool output or the &lt;code&gt;speakers&lt;/code&gt; table and fail when one doesn&amp;#39;t, would have flagged this trace. You can write exactly that as a &lt;a href=&quot;https://blog.sentry.io/evals-are-just-tests-so-why-arent-engineers-writing-them/&quot;&gt;vitest-eval&lt;/a&gt; with a small custom scorer that diffs the named entities against the database, no LLM judge required.&lt;/p&gt;
&lt;p&gt;If that check had been running in CI, it might well have caught the fabrication before I did.&lt;/p&gt;
&lt;p&gt;Nobody adds a &amp;quot;don&amp;#39;t name speakers that aren&amp;#39;t in the database&amp;quot; assertion on day one. You add it after you watch a model do it, then double down when you question it. The eval is the thing you write down afterward. Reading is what tells you it is worth writing. I have not heard of eval-driven development just yet, but I&amp;#39;ll be damned.&lt;/p&gt;
&lt;p&gt;You don&amp;#39;t have to wait for production to catch the whole category, though, even if you wait to catch the specific case. You can&amp;#39;t enumerate how people will phrase questions, so don&amp;#39;t try to test the open world; test the contract you designed instead. That&amp;#39;s the handful of intents the agent is meant to handle, plus the questions whose answer isn&amp;#39;t in your data at all. &amp;quot;Who are the famous speakers?&amp;quot; against an empty &lt;code&gt;speakers&lt;/code&gt; table is exactly that second kind, an out-of-data case you write on purpose to see whether a model grounds its answer or fabricates one. Run it against the open model before you ship and it fails the same way, no live traffic required.&lt;/p&gt;
&lt;h2&gt;The decision an eval can&amp;#39;t make for you&lt;/h2&gt;
&lt;p&gt;Run the groundedness check across the open model and Claude and you get the quality gap, and the cost and latency are already on every trace, so you can lay the whole cost, quality, and speed tradeoff out as a table.&lt;/p&gt;
&lt;p&gt;What the eval won&amp;#39;t do is pick the point on it. It can tell me the open model scores lower at a fraction of the price; it can&amp;#39;t tell me whether that lower score is acceptable for an anonymous user asking about a conference, or worth paying to fix. A failing eval says &amp;quot;wrong.&amp;quot; It doesn&amp;#39;t say &amp;quot;move anonymous users to a pricier model,&amp;quot; or &amp;quot;this is fine for kick-the-tires traffic and not worth the spend.&amp;quot; That is a judgment call about what a wrong answer costs and who&amp;#39;s on the other end of it, and it stays with me.&lt;/p&gt;
&lt;p&gt;So once I&amp;#39;d read enough traces, the fix wasn&amp;#39;t just one thing. It was a menu of options, and every one is a tradeoff:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Give the free tier a better model.&lt;/strong&gt; Fixes the quality, raises the bill and the latency, on exactly the traffic I was trying to keep cheap.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fix the routing&lt;/strong&gt; so a &amp;quot;who&amp;#39;s speaking&amp;quot; question never lands on a path that can&amp;#39;t answer it. Cheapest fix, narrowest.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tighten the prompt:&lt;/strong&gt; never name a session, speaker, or affiliation a tool didn&amp;#39;t return, and if you can&amp;#39;t answer, say so.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write the groundedness eval&lt;/strong&gt; so it can&amp;#39;t regress quietly later.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decide it&amp;#39;s fine.&lt;/strong&gt; It is an anonymous user asking about a conference, and &amp;quot;I can&amp;#39;t find that&amp;quot; is a perfectly good answer to ship for free.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I kept the cheap model, fixed the routing, tightened the prompt, and wrote the eval. I could only make that call because I&amp;#39;d read enough to know how badly it landed.&lt;/p&gt;
&lt;p&gt;And once you read a few agent traces, you start to develop a sense of what feels right, what feels wrong and things to look out for. Things an eval would not be able to tell you.&lt;/p&gt;
&lt;p&gt;You start to develop “taste” (is this overplayed yet?).&lt;/p&gt;
&lt;h2&gt;Make the traces easy to find&lt;/h2&gt;
&lt;p&gt;None of this works if you can&amp;#39;t find the trace in the first place, which is the part of &lt;a href=&quot;https://blog.sentry.io/ai-agent-observability-developers-guide-to-agent-monitoring/&quot;&gt;agent tracing&lt;/a&gt; nobody demos.&lt;/p&gt;
&lt;p&gt;My agent records inputs and outputs in its telemetry, so the whole exchange and every tool result land in &lt;a href=&quot;https://sentry.io/solutions/ai-observability/&quot;&gt;Sentry&lt;/a&gt; with a stable &lt;code&gt;conversation_id&lt;/code&gt; attached:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;experimental_telemetry: {
  isEnabled: true,
  functionId: &amp;quot;conference-scheduler.schedule&amp;quot;,
  recordInputs: true,
  recordOutputs: true,
  metadata: {
    agent: &amp;quot;schedule&amp;quot;,
    model_id: config.id,
    conversation_id: context.conversationId,
  },
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A teammate went further on their own agent. They log the first user message with a few attributes and a link straight back to the trace, so when something looks off they search the text instead of their memory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;Sentry.logger.info(&amp;quot;agent conversation started&amp;quot;, {
  conversation_id: conversationId,
  first_message: messages[0].content,
  user_tier: identity.tier,
  conversation_url: `https://your-app.com/admin/conversations/${conversationId}`,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The useful part is that the agent trace lands next to the errors, performance data, and logs from the same request. When an answer looks wrong, you can read it and then follow it to the empty query or the bad route behind it, which is the step a standalone eval score leaves you to guess at.&lt;/p&gt;
&lt;h2&gt;Read the agent traces before you reach for the fix&lt;/h2&gt;
&lt;p&gt;Picking a model, routing a question, writing a prompt, deciding what to test: these are all tradeoffs, and none of them come with a dashboard that tells you whether you got them right. An eval will tell you when a known thing regresses. It will not tell you what you haven&amp;#39;t thought to check yet, and it will not make the cost-versus-quality call for the people on your free tier.&lt;/p&gt;
&lt;p&gt;So before you swap a model or add a judge, go read some traces. That is less manual than it used to be. Point your coding agent at the &lt;a href=&quot;https://mcp.sentry.dev&quot;&gt;Sentry MCP&lt;/a&gt; and it can run &lt;code&gt;search_ai_conversations&lt;/code&gt; to surface the traces worth reading, then &lt;code&gt;get_ai_conversation_details&lt;/code&gt; to pull the transcript with every tool call and the input and output it actually returned.&lt;/p&gt;
&lt;p&gt;Read the traces, then make the call your eval can&amp;#39;t.&lt;/p&gt;
</content:encoded></item><item><title>Next.js already traces your requests. Here&apos;s how to export them with OpenTelemetry.</title><link>https://blog.sentry.io/nextjs-export-traces-opentelemetry/</link><guid isPermaLink="true">https://blog.sentry.io/nextjs-export-traces-opentelemetry/</guid><description>Next.js creates OpenTelemetry traces automatically. Learn how to export them to Sentry or any OTLP backend with @vercel/otel.</description><pubDate>Mon, 29 Jun 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Traces are a goldmine of information that can help you, &lt;em&gt;or your AI&lt;/em&gt;, find slow pages and fix them.&lt;/p&gt;
&lt;p&gt;Next.js comes out of the box with support for &lt;a href=&quot;https://sentry.io/product/tracing/&quot;&gt;tracing&lt;/a&gt;. Incoming requests, &lt;code&gt;fetch()&lt;/code&gt; calls, middleware, and server-side rendering are all wired up and ready to send traces to any OpenTelemetry-compatible backend.&lt;/p&gt;
&lt;p&gt;The catch is, unless you configure an exporter, you&amp;#39;ll never see those traces.&lt;/p&gt;
&lt;p&gt;With a few lines of code and some help from the &lt;a href=&quot;https://vercel.com/docs/tracing/instrumentation&quot;&gt;@vercel/otel&lt;/a&gt; library, your app can export those traces to any platform that accepts &lt;a href=&quot;https://sentry.io/solutions/opentelemetry/&quot;&gt;OpenTelemetry&lt;/a&gt; data, including Sentry.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(And if you&amp;#39;re trying to decide between OTLP and the Sentry SDK, we&amp;#39;ll cover that too.)&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Why Next.js traces matter&lt;/h2&gt;
&lt;p&gt;When a page in your Next.js app is slow, the hard part is figuring out &lt;em&gt;which&lt;/em&gt; part of the request is responsible. It might be middleware, server-side rendering, an API route, a database query, an upstream &lt;code&gt;fetch()&lt;/code&gt; call, or a custom function you wrote.
Without a trace of how the request actually executed, you end up working backwards from symptoms: reproducing locally, adding logs, guessing at likely bottlenecks, and hoping production behaves the same way your machine does.&lt;/p&gt;
&lt;p&gt;With tracing, each API call, page load, database query, and more, is recorded as a &lt;a href=&quot;https://opentelemetry.io/docs/concepts/signals/traces/#spans&quot;&gt;span&lt;/a&gt; in an execution timeline. A &lt;em&gt;trace&lt;/em&gt; groups all of the spans from a single request into a waterfall, so you can see exactly where time was spent.&lt;/p&gt;
&lt;p&gt;In this trace, the top-level &lt;code&gt;GET /api/auth/[...nextauth]&lt;/code&gt; span represents the incoming request. Under it, Next.js creates additional spans for every step in that request; resolving page components, executing the API route, and starting the response.
With this full hierarchy, we can see exactly where time was spent during that request.&lt;/p&gt;
&lt;p&gt;In the Node runtime, you&amp;#39;ll begin to enrich this timeline with your own custom spans that add more app-specific context to each trace (we&amp;#39;ll walk through some examples down below).&lt;/p&gt;
&lt;p&gt;Edge runtime traces can still include automatic spans from Next.js through &lt;code&gt;@vercel/otel&lt;/code&gt;, but &lt;code&gt;@vercel/otel&lt;/code&gt; does not support custom spans on Edge.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://opentelemetry.io/docs/what-is-opentelemetry/&quot;&gt;OpenTelemetry&lt;/a&gt; is an open standard for recording and transporting a variety of telemetry data, like traces, to any backend that will support it.
In order to get this data out of your app, you just need to tell OpenTelemetry where to send it.&lt;/p&gt;
&lt;h2&gt;Configuring OpenTelemetry for Next.js&lt;/h2&gt;
&lt;p&gt;Next.js already has server-side tracing enabled, OpenTelemetry just needs to know where to send it.&lt;/p&gt;
&lt;p&gt;Vercel offers a library, &lt;a href=&quot;https://vercel.com/docs/tracing/instrumentation&quot;&gt;@vercel/otel&lt;/a&gt;, that configures the OpenTelemetry SDK for you and handles the differences between the Node and Edge runtimes automatically.
We&amp;#39;ll use it to configure the SDK and export the traces to Sentry.&lt;/p&gt;
&lt;p&gt;This is particularly useful if you have an existing OpenTelemetry stack, and are self-hosting Next.js. If you are hosting on Vercel, there are &lt;a href=&quot;https://www.youtube.com/watch?v=LQBvbQcbpIY&quot;&gt;other options available&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sentry provides a &lt;a href=&quot;https://docs.sentry.io/concepts/otlp/direct/traces/&quot;&gt;direct OTLP endpoint&lt;/a&gt; that you can use to send OpenTelemetry data to, but you can use any OTLP-compatible backend.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// instrumentation.ts



  registerOTel({ serviceName: &amp;quot;next-app&amp;quot; });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next.js automatically loads &lt;code&gt;instrumentation.ts&lt;/code&gt; from the project root at startup. The &lt;code&gt;register&lt;/code&gt; function runs once before any request is handled, providing a good hook to configure the OpenTelemetry SDK.&lt;/p&gt;
&lt;p&gt;That&amp;#39;s essentially all you need to add to your app. Exporting the traces to Sentry only requires two environment variables, which we&amp;#39;ll cover in a second.&lt;/p&gt;
&lt;p&gt;You can read the full setup guide for @vercel/otel in the &lt;a href=&quot;https://vercel.com/docs/tracing/instrumentation&quot;&gt;Vercel docs&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Adding your own spans&lt;/h2&gt;
&lt;p&gt;Even though Next.js comes pre-instrumented with tracing, the spans it emits are generic and don&amp;#39;t include any unique attributes about your specific business logic.
It&amp;#39;s a good and useful starting point, but to really get the most out of tracing, you&amp;#39;ll want to add your own spans around critical parts of your application.&lt;/p&gt;
&lt;p&gt;Add your own spans around important business operations that you would name during an incident: generating an invoice, charging a card, resizing an uploaded image, calling an AI model, scoring recommendations, syncing a customer to a third-party API, etc.&lt;/p&gt;
&lt;p&gt;Don&amp;#39;t wrap every helper function. Start with high-level operations that impact user experience and business metrics.&lt;/p&gt;
&lt;p&gt;For span names, use consistent action-oriented names like &lt;code&gt;invoice.generate-pdf&lt;/code&gt; or &lt;code&gt;ai.summarize-ticket&lt;/code&gt;.
Dotted notation for naming is a common convention. It&amp;#39;s a useful way to group related spans by domain, and you can use them to scope your search when aggregating later.
For example, you might start by investigating all &lt;code&gt;invoice&lt;/code&gt; spans, and then narrow down to a specific operation like &lt;code&gt;invoice.generate-pdf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For attributes, try to add context that will help you filter and group traces when investigating performance; feature flags, plan tiers, batch sizes, item counts, payload sizes, generated file sizes, etc.
Avoid raw user IDs, emails, secrets, prompts, or anything with very high cardinality unless your backend explicitly supports that use case.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s look at some spans we might add to an invoice generator, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;

const tracer = trace.getTracer(&amp;quot;next-app&amp;quot;);


  return tracer.startActiveSpan(&amp;quot;invoice.generate-pdf&amp;quot;, async (span) =&amp;gt; {
    try {
      span.setAttribute(&amp;quot;invoice.line_items_count&amp;quot;, invoice.lineItems.length);
      span.setAttribute(&amp;quot;invoice.locale&amp;quot;, invoice.locale);
      span.setAttribute(&amp;quot;invoice.template&amp;quot;, invoice.template);
      span.setAttribute(&amp;quot;customer.plan&amp;quot;, invoice.customer.plan);

      const pdf = await renderInvoicePdf(invoice);

      span.setAttribute(&amp;quot;invoice.pdf_size_bytes&amp;quot;, pdf.byteLength);

      return pdf;
    } finally {
      span.end();
    }
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, if invoice generation gets slow, you can search for &lt;code&gt;invoice.generate-pdf&lt;/code&gt;, group those spans by template, locale, or customer plan, and quickly see whether large invoices, a specific PDF template, or one customer segment is causing the slowdown.&lt;/p&gt;
&lt;p&gt;An important caveat of custom spans for &lt;code&gt;@vercel/otel&lt;/code&gt; is that they are only supported in the Node runtime. The &lt;code&gt;@vercel/otel&lt;/code&gt; library does instrument automatic spans in the Edge runtime, but you cannot add your own.
To add custom spans in the Edge runtime, consider using the &lt;a href=&quot;https://docs.sentry.io/platforms/javascript/guides/nextjs/&quot;&gt;@sentry/nextjs&lt;/a&gt; SDK instead.&lt;/p&gt;
&lt;h2&gt;Sending traces to an OTLP backend&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://opentelemetry.io/docs/specs/otlp/&quot;&gt;OTLP&lt;/a&gt; is the standard protocol for transporting OpenTelemetry traces. An OTLP backend is the destination that receives those traces, stores them, and gives you a UI for searching, filtering, and viewing trace waterfalls. That could be an observability platform like Sentry, or another OpenTelemetry-compatible service.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@vercel/otel&lt;/code&gt; expects and honors the standard &lt;code&gt;OTEL_EXPORTER_OTLP_*&lt;/code&gt; environment variables for configuring the exporter. So, to export to Sentry&amp;#39;s &lt;a href=&quot;https://docs.sentry.io/concepts/otlp/direct/traces/&quot;&gt;direct OTLP endpoint&lt;/a&gt;, you&amp;#39;ll need to set two environment variables: an endpoint URL and an authentication header.&lt;/p&gt;
&lt;p&gt;You&amp;#39;ll find your endpoint URL and public key under &lt;a href=&quot;https://sentry.io/orgredirect/organizations/:orgslug/settings/projects/:projectId/keys/&quot;&gt;&lt;strong&gt;Project Settings → Client Keys (DSN)&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Set two environment variables:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Set those wherever you deploy your app so they exist at runtime. Once set, you should start immediately seeing traces populating your Sentry &lt;a href=&quot;https://docs.sentry.io/concepts/key-terms/tracing/trace-view/&quot;&gt;Trace View&lt;/a&gt;.
No Sentry SDK, no collector, minimal code change.&lt;/p&gt;
&lt;h2&gt;Choosing between OTLP and the Sentry SDK&lt;/h2&gt;
&lt;p&gt;OpenTelemetry is vendor-neutral and excellent when you are already deep in the ecosystem. But, if you&amp;#39;re configuring tracing for the first time, my recommendation, especially for Next.js, is to use the Sentry SDK.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@vercel/otel&lt;/code&gt; is easy to configure, but it doesn&amp;#39;t capture the whole picture. It can export useful server-side spans from the Node and Edge runtimes, but it won&amp;#39;t give you the browser side of the trace, Sentry issues, or the rest of the debugging context that comes from the SDK.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;&lt;code&gt;@vercel/otel&lt;/code&gt; direct to Sentry OTLP&lt;/th&gt;
&lt;th&gt;&lt;code&gt;@sentry/nextjs&lt;/code&gt; SDK&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Node server tracing&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edge runtime tracing&lt;/td&gt;
&lt;td&gt;Automatic server spans only&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom spans&lt;/td&gt;
&lt;td&gt;Node runtime only&lt;/td&gt;
&lt;td&gt;Node, Edge, and browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser tracing&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error monitoring&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source-mapped stack traces&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session Replay&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logs&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The practical choice is simpler than the number of integrations makes it look. If your team relies on Sentry to find, group, and debug production issues, use the SDK. If you already have an OpenTelemetry pipeline, want to migrate in stages, or only need standards-based server traces in Sentry, direct OTLP is the leaner option.&lt;/p&gt;
&lt;p&gt;One compatibility note: don&amp;#39;t initialize &lt;code&gt;@vercel/otel&lt;/code&gt; and the Sentry SDK&amp;#39;s OpenTelemetry setup in the same app. Both register a tracer provider. If you already manage your own &lt;code&gt;NodeTracerProvider&lt;/code&gt; and want to add Sentry alongside it, set &lt;code&gt;skipOpenTelemetrySetup: true&lt;/code&gt; in &lt;code&gt;Sentry.init()&lt;/code&gt; and follow the &lt;a href=&quot;https://docs.sentry.io/platforms/javascript/guides/nextjs/opentelemetry/custom-setup/&quot;&gt;custom setup guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If your app runs on Vercel and you want platform-level forwarding instead of app-level exporter configuration, &lt;a href=&quot;https://docs.sentry.io/product/drains/vercel/&quot;&gt;Vercel Drains&lt;/a&gt; can send logs and traces to Sentry too. Just remember that drains and direct OTLP still do not replace SDK-based error monitoring.&lt;/p&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;Once your Next.js traces are flowing into Sentry, turn them into something your team can act on. Use &lt;a href=&quot;https://docs.sentry.io/product/monitors-and-alerts/monitors/&quot;&gt;Monitors&lt;/a&gt; to watch the routes and operations that matter most: auth flows, checkout, dashboards, API routes, and any page users complain about being slow. Monitors can evaluate thresholds on spans and other application signals, then turn those conditions into issues when they cross the line.&lt;/p&gt;
&lt;p&gt;Pair those monitors with &lt;a href=&quot;https://docs.sentry.io/product/monitors-and-alerts/alerts/&quot;&gt;Alerts&lt;/a&gt; so the right people hear about new or worsening issues in &lt;a href=&quot;https://docs.sentry.io/integrations/notification-incidents/slack/&quot;&gt;Slack&lt;/a&gt;, email, &lt;a href=&quot;https://develop.sentry.dev/integrations/pagerduty/&quot;&gt;PagerDuty&lt;/a&gt;, &lt;a href=&quot;https://docs.sentry.io/integrations/integration-platform/webhooks/&quot;&gt;webhooks&lt;/a&gt;, or whatever workflow your team uses. That gives you a path from trace data, to a triageable issue, to the action your team needs to take.&lt;/p&gt;
&lt;p&gt;Then build &lt;a href=&quot;https://docs.sentry.io/product/dashboards/&quot;&gt;dashboards&lt;/a&gt; around the questions you want answered every week: which routes are slowest, which custom spans are regressing, and whether performance changed after a deploy.&lt;/p&gt;
&lt;p&gt;Traces are most useful when they become part of your operational loop: monitor the important paths, alert when they drift, use dashboards to see whether the fixes actually helped, and try &lt;a href=&quot;https://docs.sentry.io/product/ai-in-sentry/seer/#seer-agent&quot;&gt;Seer Agent&lt;/a&gt; to automatically inspect traces and explain what&amp;#39;s causing a slow or failing request.&lt;/p&gt;
</content:encoded></item><item><title>Better, faster, less wrong: Enhancing issue grouping</title><link>https://blog.sentry.io/enhancing-issue-grouping/</link><guid isPermaLink="true">https://blog.sentry.io/enhancing-issue-grouping/</guid><description>Sentry&apos;s new AI grouping model prevents 20% more duplicate issues while cutting incorrect merges in half. Here&apos;s how we trained and deployed it.</description><pubDate>Fri, 12 Jun 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Sentry&amp;#39;s job is to tell you when your app breaks. To do that, we group individual errors into issues. First by &lt;a href=&quot;https://docs.sentry.io/concepts/data-management/event-grouping/&quot;&gt;fingerprinting&lt;/a&gt;, which lexically matches errors based on their structure, then by an AI fallback: when fingerprinting can&amp;#39;t find a match, an ML model compares the new error&amp;#39;s stacktrace against existing issues and merges it if they&amp;#39;re semantically similar. We recently upgraded the model, preventing 20% more duplicate issues from being created while halving the rate of incorrect merges.&lt;/p&gt;
&lt;p&gt;AI-powered issue grouping is enabled by default and live for all Sentry customers.&lt;/p&gt;
&lt;h2&gt;What makes grouping hard&lt;/h2&gt;
&lt;p&gt;We talked about v1 of AI grouping in &lt;a href=&quot;https://blog.sentry.io/how-sentry-decreased-issue-noise-with-ai/&quot;&gt;Using a transformer-based text embeddings model to reduce Sentry alerts by 40% and cut through noise&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A good grouping algorithm minimizes both &lt;strong&gt;undergrouping&lt;/strong&gt; and &lt;strong&gt;overgrouping&lt;/strong&gt;. Minimizing undergrouping means Sentry isn&amp;#39;t spamming your feed with new issues for the same underlying problem. Minimizing overgrouping means Sentry always tells you about new categories of errors your app is experiencing. Striking the right balance between these can be tricky and subjective.&lt;/p&gt;
&lt;p&gt;Overgrouping is arguably the more sinister failure mode. When Sentry incorrectly merges an error into an issue, we&amp;#39;re hiding a problem that should be addressed by a different work stream; this error may have a different priority, root cause, and fix.&lt;/p&gt;
&lt;p&gt;To demonstrate how easy it is to mess this up, here&amp;#39;s a real example of 2 nearly identical stacktraces from the Sentry repo that have distinct root causes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Stacktrace A
ReadTimeoutError / TimeoutError
  autofix.py::generate_summary_and_run_automation
  issue_summary.py::get_issue_summary → _generate_summary
  → run_automation → get_autofix_state        # &amp;lt;-- diverges here
    utils.py::make_get_autofix_state_request
    signed_seer_api.py::make_signed_seer_api_request → urlopen

# Stacktrace B
ReadTimeoutError / TimeoutError
  autofix.py::generate_summary_and_run_automation
  issue_summary.py::get_issue_summary → _generate_summary
  → run_automation → get_and_update_group_fixability_score  # &amp;lt;-- diverges here
    issue_summary.py::_generate_fixability_score
    signed_seer_api.py::make_signed_seer_api_request → urlopen
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both errors are timeouts from the &lt;a href=&quot;https://sentry.io/product/seer/autofix/&quot;&gt;Seer Autofix feature&lt;/a&gt; and share the same request path. The needle in the haystack is in two stacktrace frames. The first timeout is from the &lt;code&gt;get_autofix_state&lt;/code&gt; flow: a DB query that&amp;#39;ll run on a standard web server. The second timeout is from the &lt;code&gt;_generate_fixability_score&lt;/code&gt; flow: an ML model call that&amp;#39;ll run on a GPU server through a separate connection pool with a 2x higher timeout threshold. Improving client-side and server-side handling of these requests entails independent work. Yet the v1 model merges these errors into a single issue. The v2 model separates them.&lt;/p&gt;
&lt;p&gt;Grouping stacktraces is a nuanced task, not the kind that embeddings APIs are trained to handle. The heavyweight gemini-embeddings-2 model, for example, &lt;a href=&quot;https://github.com/getsentry/grouping-trainer/tree/main/eval/comparisons/test_full3/gemini-embedding-2_dim3072_vs_large-no-prefix_dim64&quot;&gt;flops on our task&lt;/a&gt;. While &lt;a href=&quot;https://huggingface.co/jinaai/jina-embeddings-v2-base-code&quot;&gt;Jina AI&amp;#39;s code embeddings model&lt;/a&gt; was accurate and efficient for v1 of AI grouping, we knew we could do better than anything off the shelf. We&amp;#39;re sitting on a trove of production stacktraces, plus two years of v1 failure modes the team has internalized as tribal knowledge. LLMs let us apply that knowledge at scale through a labeling algorithm we ran on hundreds of thousands of stacktraces. The v2 model is trained on those labels and has been powering AI grouping since April.&lt;/p&gt;
&lt;h2&gt;Here&amp;#39;s how v2 is doing in production&lt;/h2&gt;
&lt;h3&gt;We&amp;#39;re making fewer issues&lt;/h3&gt;
&lt;p&gt;AI grouping is preventing 70% of all new issues from being created, up from 50% before we started rolling out v2 on April 22. Among 3,800 large Sentry projects (projects ingesting at least 100 new issues/day with stable error volume before and after v2), 18% saw their rate of AI-prevented issues double since v2.&lt;/p&gt;
&lt;h3&gt;Overgrouping has been cut in half&lt;/h3&gt;
&lt;p&gt;v1&amp;#39;s overgrouping rate, or its rate of incorrect merges, is 8%. While this number doesn&amp;#39;t sound terrible in the aggregate, the kicker is that the v1 model has a tendency to overgroup errors from certain platforms. Platform biases can significantly harm the Sentry experience at the project level. In a small but focused exploration, we found projects whose v1 overgrouping rates ranged from 30% to 60%. It&amp;#39;s not uncommon to find issues in these projects that are bloated with errors representing many distinct root causes.&lt;/p&gt;
&lt;p&gt;v2&amp;#39;s overgrouping rate is 4%, and is low across all platforms. All projects in the cohort above now overgroup 2-15% of the time while maintaining or improving on v1 merge rates.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;v2 improves across every platform where v1 significantly overgrouped. Merge rate is the fraction of new errors which are merged into an existing issue. The overgrouping rate is the fraction of these merges which are incorrect; the error is in an issue that it doesn&amp;#39;t truly belong to.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;How we trained v2&lt;/h2&gt;
&lt;p&gt;Sampling and labeling data were, by far, the most critical parts of our training pipeline. We sampled stacktraces from hundreds of consenting Sentry projects, selecting for a wide variety in project merge rates and stacktrace lengths, and partially stratified by platform to counter Sentry&amp;#39;s majority JavaScript makeup. We also increased sample weights for stacktrace pairs that were closer to the v1 model&amp;#39;s decision boundary, based on the hypothesis that these pairs are the ones where a successor stands to gain the most.&lt;/p&gt;
&lt;p&gt;We labeled the data by prompting Claude Sonnet 4.5 with a thinking budget of 1,024 tokens. The prompt was carefully iterated to follow Sentry&amp;#39;s guidelines around issue grouping, including weighing the error&amp;#39;s underlying cause over surface-level semantics. We measured the prompt&amp;#39;s accuracy against expert-labeled stacktrace pairs from internal Sentry projects. The expert labelers were Sentry&amp;#39;s grouping czars: employees who have mulled over stacktrace similarity for years. We err on the side of avoiding overgrouping in our own labels and instructed Claude to do the same.&lt;/p&gt;
&lt;p&gt;We then trained &lt;a href=&quot;https://huggingface.co/lightonai/modernbert-embed-large&quot;&gt;lightonai/modernbert-embed-large&lt;/a&gt; on these labeled stacktrace pairs. The training data contains hundreds of thousands of pairs of stacktraces ranging from 10 to 8192 tokens. Most training runs tested genuine improvement ideas. Some runs were for the love of the game. A couple of unique training choices are that we use a loss function from 2005, group the dataloader&amp;#39;s scan order to deduplicate forwards passes, and we do something funny with DDP after losing an afternoon failing to install &lt;code&gt;flash-attn&lt;/code&gt; for &lt;code&gt;varlen&lt;/code&gt;. Our findings, including interesting experiments that did &lt;em&gt;not&lt;/em&gt; yield improvements, are &lt;a href=&quot;https://github.com/getsentry/grouping-trainer/blob/main/notes.md&quot;&gt;documented in the grouping-trainer repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since we were running experiments in parallel to eke out accuracy, we built some &lt;a href=&quot;https://github.com/getsentry/grouping-trainer/blob/main/src/grouping_trainer/launch.py&quot;&gt;baby training infra&lt;/a&gt; to reduce headaches induced by GPU stockouts. We&amp;#39;ve gotten up to 20 L4, A100, and H100 GPUs going at the same time (a modest fleet for a team of our size), and all running within 30 minutes of being triggered.&lt;/p&gt;
&lt;p&gt;Our training, offline eval, and baby infra code is available at &lt;a href=&quot;https://github.com/getsentry/grouping-trainer&quot;&gt;getsentry/grouping-trainer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On the evaluation side, we now measure overgrouping in production. Overgrouping has been a silent but deadly failure mode in the past. Uncovering it used to require manually scrutinizing merged issues in the UI based on customer reports. Today, we have a pipeline that can batch-label 1000s of merged issues from a SQL query.&lt;/p&gt;
&lt;h2&gt;Modernizing v2 inference&lt;/h2&gt;
&lt;p&gt;We also upgraded the v2 model&amp;#39;s inference to reduce its overhead on Sentry&amp;#39;s ultra-hot error ingestion path. AI grouping v2 is 6x faster than v1, uses less GPU memory, and saves hundreds of GBs of DB space.&lt;/p&gt;
&lt;p&gt;The most impactful modernization is embedding truncation. Because the v2 model is trained via &lt;a href=&quot;https://arxiv.org/abs/2205.13147&quot;&gt;Matryoshka Representation Learning&lt;/a&gt;, truncating v2 embeddings from 768 dimensions to 64 sacrificed just 2% in accuracy while reducing p50 HNSW lookup times by 4x, p50 insertion times by 20x, and pgvector DB storage by 12x.&lt;/p&gt;
&lt;p&gt;We also run the model in &lt;code&gt;bfloat16&lt;/code&gt; and enabled PyTorch&amp;#39;s SDPA to get an easy inference speedup and eliminate CUDA OOMs in production. These changes are a one-liner thanks to &lt;a href=&quot;https://huggingface.co/&quot;&gt;Hugging Face&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt; model = SentenceTransformer(
     &amp;quot;lightonai/modernbert-embed-large&amp;quot;,
+    model_kwargs={&amp;quot;dtype&amp;quot;: torch.bfloat16, &amp;quot;attn_implementation&amp;quot;: &amp;quot;sdpa&amp;quot;}
 )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we compiled the model to fit our latency-sensitive + plain PyTorch server setup, reducing p50 model inference latency by almost 3x compared to no compilation. In case you have a similar setup and a long-tailed token distribution, we benchmarked this CUDA graph compilation strategy and made it &lt;a href=&quot;https://github.com/kddubey/sentence-transformers/tree/kddubey/examples/compilation/examples/sentence_transformer/applications/compilation&quot;&gt;available on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All of these changes were stress-tested offline by running them through a load test that closely mimics production: start with a pgvector DB backfilled with real projects&amp;#39; stacktraces, and concurrently send the app real, new stacktraces from those projects.&lt;/p&gt;
&lt;h2&gt;How we&amp;#39;ve enabled seamless model upgrades&lt;/h2&gt;
&lt;p&gt;Upgrading ML models for stateless inference is conceptually simple: load in the new model and serve it. AI grouping is far from stateless: it inserts embeddings at a rate proportional to issue creation, and stores this data for up to 90 days. Naively replacing the v1 model with the v2 one would cause a massive surge in new issues, as v2 has nothing in the database to match against. This meant we needed to ship the new model and backfill its embeddings without hurting the customer experience.&lt;/p&gt;
&lt;p&gt;We considered a full backfill (like v1, which took months), waiting 90 days to accumulate v2 embeddings before switching, or going live with v2 immediately and falling back to v1 whenever v2 doesn&amp;#39;t have enough data. We went with option 3: customers get v2&amp;#39;s benefits from day one, and v2 can only improve on v1&amp;#39;s decisions, never undo them.&lt;/p&gt;
&lt;p&gt;The mechanism hinges on two parameters we added to Seer&amp;#39;s grouping API: &lt;code&gt;model&lt;/code&gt; and &lt;code&gt;training_mode&lt;/code&gt;. When a project is opted into v2 via a feature flag, Sentry sends stacktraces to Seer with &lt;code&gt;model=v2&lt;/code&gt;. Seer searches the v2 embedding space first; if it comes up empty, it falls back to v1.&lt;/p&gt;
&lt;p&gt;There&amp;#39;s a subtlety, though: Seer uses &lt;strong&gt;threshold-gated indexing&lt;/strong&gt;, which means embeddings are only stored when no sufficiently close match already exists in the index. This keeps the HNSW graph compact and improves search efficiency: since we only need any match within the threshold rather than the true nearest neighbor, a sparser graph with bounded edges (m=16) is all we need. The consequence, however, is that v2&amp;#39;s corpus would only grow from genuinely new events, leaving existing groups without v2 representations.&lt;/p&gt;
&lt;p&gt;That&amp;#39;s where &lt;code&gt;training_mode&lt;/code&gt; comes in. For group hashes that were previously only sent under v1, Sentry fires a request with &lt;code&gt;training_mode=true&lt;/code&gt;. Seer stores the v2 embedding but returns no grouping result, which bypasses near-duplicate suppression to deliberately backfill the index. This populates v2&amp;#39;s database in real time, piggybacking on live traffic instead of running a separate job.&lt;/p&gt;
&lt;p&gt;We rolled out project by project, starting internally. The entire migration took about 6 weeks with zero customer-reported regressions. And the infrastructure is reusable. If we train a v3 tomorrow, the upgrade path is the same: add a column, flip a flag, and let the fallback chain handle the transition.&lt;/p&gt;
&lt;h2&gt;What&amp;#39;s next?&lt;/h2&gt;
&lt;p&gt;Labeling more data and finetuning a much bigger model will help. A more interesting line of research is to improve the model&amp;#39;s input. Today, the model only looks at the error&amp;#39;s stacktrace. A smarter model should be able to use signals attached to the error on ingestion, e.g., the transaction and variable values. Going a step further, what if Sentry accumulated knowledge about your app and attached relevant context dynamically: what&amp;#39;s the higher-level purpose of a transaction, how do downstream users experience an error? We&amp;#39;re currently exploring this context accumulation problem through &lt;a href=&quot;https://docs.sentry.io/product/ai-in-sentry/seer/&quot;&gt;Seer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the meantime, v2 will be chugging along, routing your stacktraces to the right issue.&lt;/p&gt;
</content:encoded></item><item><title>Catch visual regressions with Snapshots, now in beta</title><link>https://blog.sentry.io/snapshots-available-beta/</link><guid isPermaLink="true">https://blog.sentry.io/snapshots-available-beta/</guid><description>Sentry Snapshots is now in beta. Get screenshot diffing in CI to catch unintended visual changes on every commit, for any platform with a frontend.</description><pubDate>Thu, 11 Jun 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Sentry Snapshots diffs screenshots on every commit and blocks the PR if there are any visual changes so you can confirm they&amp;#39;re intentional. Users don&amp;#39;t interact with code, they interact with something they can see and touch. Snapshots gives you a lightweight way to test it.&lt;/p&gt;
&lt;p&gt;It&amp;#39;s easier than ever to change code. It&amp;#39;s also easier than ever to trade quality for speed. Modern codebases need guardrails to ensure correctness. Our goal with Snapshots is to be a check for your code and a resource to improve your workflow.&lt;/p&gt;
&lt;h2&gt;What is snapshot testing&lt;/h2&gt;
&lt;p&gt;The core flow of snapshot testing is simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Make a code change&lt;/li&gt;
&lt;li&gt;Generate screenshots of your application&lt;/li&gt;
&lt;li&gt;Compare those screenshots against a baseline&lt;/li&gt;
&lt;li&gt;Surface any changes&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In Sentry Snapshots, you upload screenshots, we diff them, then post in the PR if there are any diffs.&lt;/p&gt;
&lt;p&gt;You can then jump into the UX to see what exactly those changes are:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/snapshots-available-beta/snapshot-wipe-diff-comparison.gif&quot; alt=&quot;Sentry Snapshots wipe diff comparison mode cycling through button component changes&quot;&gt;&lt;/p&gt;
&lt;p&gt;This simple workflow adds an axis of visual coverage. Test your application looks as expected in different themes, languages, or viewports. Snapshot testing validates that you&amp;#39;re not changing more than intended.&lt;/p&gt;
&lt;h2&gt;How Sentry Snapshots works with agents&lt;/h2&gt;
&lt;p&gt;Snapshot testing as it&amp;#39;s always existed, as a guardrail for visual changes, is especially helpful for agents. Maybe you have a haphazard prompt, agent makes a change, run snapshot testing, block PR if a change is detected.&lt;/p&gt;
&lt;p&gt;But Snapshots can also be an artifact that can be &lt;em&gt;used&lt;/em&gt; by agents. Here&amp;#39;s a runthrough of how we recently used Snapshots to help make a visual change to Snapshots itself.&lt;/p&gt;
&lt;h3&gt;The issue&lt;/h3&gt;
&lt;p&gt;Multiple users gave similar feedback around our &amp;quot;Snapshot Toolbar&amp;quot;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Being confused at the initial red &amp;quot;diff mask&amp;quot; on a snapshot&lt;/li&gt;
&lt;li&gt;Wanting to hide the mask (this was already possible)&lt;/li&gt;
&lt;li&gt;Wanting to change &amp;quot;diff&amp;quot; views, but not being sure how to&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Basically, we had discoverability issues for features that exist.&lt;/p&gt;
&lt;h3&gt;Snapshots as context&lt;/h3&gt;
&lt;p&gt;The first step was to increase our coverage. We had some tests in place, but wanted to add screenshots for different active states in our component (there are slight differences depending on the active view):&lt;/p&gt;
&lt;p&gt;To add to our test, we obviously need to find where our test is.&lt;/p&gt;
&lt;p&gt;Sentry Snapshots lets you add &lt;code&gt;context&lt;/code&gt; metadata to every image. This can be whatever you want and is designed to provide information to agents. In Sentry&amp;#39;s dogfooding of Snapshots, we pass a &lt;code&gt;source_test_file&lt;/code&gt;, which is just a path to where the file is.&lt;/p&gt;
&lt;p&gt;To add more snapshot tests, all I had to do was copy-paste the metadata to an LLM and explain what I wanted to do:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Find this snapshot test and add states for the different diff views
{
  &amp;quot;display_name&amp;quot;: &amp;quot;all controls @md&amp;quot;,
  &amp;quot;group&amp;quot;: &amp;quot;SnapshotsToolbar/light&amp;quot;,
  &amp;quot;image_file_name&amp;quot;: &amp;quot;static/app/views/preprod/snapshots/main/snapshotstoolbar-light-allcontrols-md.png&amp;quot;,
  &amp;quot;width&amp;quot;: 1984,
  &amp;quot;height&amp;quot;: 90,
  &amp;quot;tags&amp;quot;: {
    &amp;quot;area&amp;quot;: &amp;quot;snapshots&amp;quot;,
    &amp;quot;theme&amp;quot;: &amp;quot;light&amp;quot;,
    &amp;quot;viewport&amp;quot;: &amp;quot;md&amp;quot;
  },
  &amp;quot;context&amp;quot;: {
    &amp;quot;test_file_path&amp;quot;: &amp;quot;static/app/views/preprod/snapshots/main/snapshotsToolbar.snapshots.tsx&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have a good framework for writing snapshot tests, agents are very good at writing more, especially if you tell them exactly where to look. Here&amp;#39;s the new tests we produced (unique UI highlighted for example purposes):&lt;/p&gt;
&lt;p&gt;In this case, I was doing work related to snapshot tests so copy-pasting was easy, but this data is also available via the &lt;a href=&quot;https://mcp.sentry.dev/&quot;&gt;&lt;code&gt;sentry-mcp&lt;/code&gt;&lt;/a&gt; and our REST API to use in other scenarios. I used the literal &lt;code&gt;context&lt;/code&gt; prop to direct the agent, but you also have access to the produced images and diffs to pass in, which are a helpful reference for agents.&lt;/p&gt;
&lt;h4&gt;Reviewing diffs from CI&lt;/h4&gt;
&lt;p&gt;Our initial stab to improve discoverability was to show the active label. But this didn&amp;#39;t do as much as we wanted, specifically for the overlay mask.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/snapshots-available-beta/snapshot-toolbar-active-label.gif&quot; alt=&quot;SnapshotsToolbar showing the active diff mode label after the initial discoverability improvement&quot;&gt;&lt;/p&gt;
&lt;p&gt;We passed in the link to the snapshot to the LLM:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;I want to make the color picker more noticeable, but it&amp;#39;s still too subtle.
Make it have a light border on dark theme and dark border on light theme:
https://sentry.sentry.io/preprod/snapshots/299186/?selectedTypes=changed&amp;amp;selectedSnapshot=static%2Fapp%2Fviews%2Fpreprod%2Fsnapshots%2Fmain%2Fsnapshotstoolbar-dark-allcontrols-sm.png
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This triggered the agent to use the &lt;code&gt;sentry-mcp&lt;/code&gt; to get the snapshot resource, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Base image&lt;/li&gt;
&lt;li&gt;Head image&lt;/li&gt;
&lt;li&gt;Diff mask&lt;/li&gt;
&lt;li&gt;Metadata&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The agent easily made the change. Realistically, this simple change probably could have done without the resource, but the screenshot data + context metadata provides context specific to the task. The context includes narrowed screenshot data, as well as your local PR information. This is a repeatable, programmatic way of hitting your LLM with a screenshot + &amp;quot;do the thing.&amp;quot;&lt;/p&gt;
&lt;h4&gt;Snapshots as local verification&lt;/h4&gt;
&lt;p&gt;To finish the loop, we need our agent to be able to produce and diff snapshots. Producing snapshots is bespoke to the repo. The &lt;code&gt;sentry-cli&lt;/code&gt; lets you download the baseline images and diff your local set against them. We can have our agent do the diffing and then expose the results to us (or if you also like to live dangerously, have the LLM judge and commit if it looks intended).&lt;/p&gt;
&lt;p&gt;The local flow is roughly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 1. download base
sentry-cli snapshots download --app-id {your-id} --branch {main}
# 2. generate snapshots locally
# 3. diff snapshots
sentry-cli snapshots diff ./snapshots-base path-to-local --fail-on-diff --selective
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can be useful independent of an agent, but you can also have your agent do this as part of the pre-commit process or in a prompt.&lt;/p&gt;
&lt;p&gt;The local diff is exactly as expected. We pushed up the new commit, where Snapshots in CI also flagged the diff. We approved, merged, and voila:&lt;/p&gt;
&lt;p&gt;This was a simple change, but it&amp;#39;s easy to see how all of these workflows can be extended. Snapshots can be used as hyper-relevant context, which your agent can query against, make changes, and run verification loops locally.&lt;/p&gt;
&lt;p&gt;For full reference, read our &lt;a href=&quot;https://docs.sentry.io/product/snapshots/local-testing/&quot;&gt;local testing documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Snapshot testing isn&amp;#39;t perfect&lt;/h2&gt;
&lt;p&gt;Snapshot testing is not for everyone, nor is it a good fit for every repo. There is a non-zero chance you&amp;#39;ve read this far out of anger, seething to yourself that snapshot testing is a useless PITA. It can be more trouble than it&amp;#39;s worth to produce good screenshots. You may have fought with managing &amp;quot;goldens&amp;quot; or git-lfs in the past.&lt;/p&gt;
&lt;p&gt;We agree, which is why we first built Snapshots at &lt;a href=&quot;https://sentry.io/about/press-releases/sentry-acquires-emerge-tools-to-enhance-its-mobile-app-monitoring-solution/&quot;&gt;Emerge Tools&lt;/a&gt;. Our original Snapshots product focused on mobile apps and abstracted all the painful parts away and was used by companies like &lt;a href=&quot;https://www.emergetools.com/blog/posts/openai-on-mobile-development-previews-and-snapshot-testing&quot;&gt;OpenAI&lt;/a&gt; to build out ChatGPT and Codex. Sentry Snapshots takes what we thought worked well and extends the functionality to any platform.&lt;/p&gt;
&lt;p&gt;It might be true that your code is not easy to snapshot; we&amp;#39;re in the midst of improving how we use Snapshots at Sentry ourselves. But we&amp;#39;ve seen how effective a snapshot testing workflow can be and are excited to keep building it out.&lt;/p&gt;
&lt;h2&gt;Getting started with Snapshots&lt;/h2&gt;
&lt;p&gt;Sentry&amp;#39;s motto is &amp;quot;code breaks, fix it faster.&amp;quot; Snapshots is a step to prevent it from breaking at all.&lt;/p&gt;
&lt;p&gt;Snapshots is now available in beta for all users. There are recommended workflows for iOS and Android to go from Previews to Screenshots (the iOS repo was recently &lt;a href=&quot;https://x.com/openaidevs/status/2062599303231922239?s=46&quot;&gt;highlighted as part of Codex&amp;#39;s new iOS functionality&lt;/a&gt;). You can use Snapshots on any platform with a frontend.&lt;/p&gt;
&lt;p&gt;Have questions or feedback?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Join the conversation &lt;a href=&quot;https://discord.com/invite/sentry&quot;&gt;in our Discord&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Email us at &lt;a href=&quot;mailto:mobile-team@sentry.io&quot;&gt;mobile-team@sentry.io&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;New to Sentry?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://sentry.io/signup/&quot;&gt;Try Sentry for free&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Works on my machine: how we use AI to reproduce reported bugs</title><link>https://blog.sentry.io/ai-bug-reproduction/</link><guid isPermaLink="true">https://blog.sentry.io/ai-bug-reproduction/</guid><description>How Sentry&apos;s SDK team built a Claude skill to auto-reproduce bug reports, reducing triage time across 159 packages.</description><pubDate>Mon, 08 Jun 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Sentry&amp;#39;s SDK teams maintain and support SDKs for a vast ecosystem of languages and frameworks. See our &lt;a href=&quot;https://github.com/getsentry/sentry-release-registry/tree/master/sdks&quot;&gt;release registry&lt;/a&gt; for a source of truth. We&amp;#39;re currently at 159 published packages across the entire ecosystem. If you use it, we probably support it.&lt;/p&gt;
&lt;p&gt;All of these SDKs are open source and have their own &lt;a href=&quot;https://github.com/getsentry/sentry-python&quot;&gt;GitHub&lt;/a&gt; &lt;a href=&quot;https://github.com/getsentry/sentry-javascript/&quot;&gt;repositories&lt;/a&gt; that we maintain on a daily basis. And like any other open source project, we get tons of bug reports and issues on these.&lt;/p&gt;
&lt;p&gt;In this post, I&amp;#39;ll talk about a Claude skill we&amp;#39;ve been leveraging to help make our reproduction flow smoother and reduce triage time and fatigue.&lt;/p&gt;
&lt;h2&gt;Bug triage flow&lt;/h2&gt;
&lt;p&gt;Sometimes bugs are easy to fix - could have been a missing null check, a missing conditional branch or some other small oversight.&lt;/p&gt;
&lt;p&gt;Other times, they aren&amp;#39;t so easy for a plethora of reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tedious setup, or &amp;quot;boilerplate&amp;quot;, just to get the environment ready&lt;/li&gt;
&lt;li&gt;Esoteric code paths&lt;/li&gt;
&lt;li&gt;Legacy versions&lt;/li&gt;
&lt;li&gt;Edge case interactions no one thought of&lt;/li&gt;
&lt;li&gt;Data races and other concurrency problems&lt;/li&gt;
&lt;li&gt;Forked libraries with different contracts&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Boilerplate&lt;/h3&gt;
&lt;p&gt;Particularly for our SDK bugs, the boilerplate factor is quite annoying. Let&amp;#39;s take a &lt;a href=&quot;https://github.com/getsentry/sentry-python/issues/5955&quot;&gt;recent example&lt;/a&gt;. To reproduce this, we would need to setup the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A Python venv with the correct version&lt;/li&gt;
&lt;li&gt;A new Django boilerplate app with the correct version&lt;/li&gt;
&lt;li&gt;A Sentry SDK with the correct version&lt;/li&gt;
&lt;li&gt;Create a Django View that reproduces and showcases the exact problem which is applicable only to HTTPS proxies&lt;/li&gt;
&lt;li&gt;Run everything, trigger the view and hope that it shows the problem in question&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this is necessary just to acknowledge that the problem the original user reported is real and replicable. Once reproduced, it&amp;#39;s typically much easier to roll out the actual fix.&lt;/p&gt;
&lt;h3&gt;Reproduction papertrail&lt;/h3&gt;
&lt;p&gt;Another recurring discussion within the teams was how to keep track of all these one-off boilerplate apps that we used to test SDK logic, and reproduce/fix problems.&lt;/p&gt;
&lt;p&gt;Ideally we would have a shared repository of these apps with backlinks to the issues, but no one wanted the burden of maintaining yet another collection of apps on top of everything else we already do. Several SDK engineers had their own ad-hoc collection of apps they used for their day-to-day SDK development.&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;repro&lt;/code&gt; skill + repository&lt;/h2&gt;
&lt;p&gt;Enter LLMs. Turns out LLMs are pretty good at doing some of the tedious stuff mentioned above.&lt;/p&gt;
&lt;p&gt;Even if they cannot get to the root of a hairy problem, they at least set up the boilerplate and give me a playground with all the correct parameters which I can move forward with, massively reducing tedium.&lt;/p&gt;
&lt;p&gt;So I wrote up and iterated on a &lt;a href=&quot;https://github.com/getsentry/repro/blob/main/.claude/skills/repro/SKILL.md&quot;&gt;Claude skill&lt;/a&gt; that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Takes a GitHub issue URL as input&lt;/li&gt;
&lt;li&gt;Parses the SDK language, issue number&lt;/li&gt;
&lt;li&gt;Gathers metadata on language version, framework version, SDK version&lt;/li&gt;
&lt;li&gt;Makes a new directory and branch from the &lt;code&gt;language/issue-number&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Attempts to create a minimal reproduction using standard tooling for the language (&lt;code&gt;uv&lt;/code&gt;, &lt;code&gt;npm&lt;/code&gt;, &lt;code&gt;bundle&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;Tries to run the reproduction, bails out if it&amp;#39;s too complicated&lt;/li&gt;
&lt;li&gt;Writes up clear instructions for running the reproduction&lt;/li&gt;
&lt;li&gt;Makes a PR&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optionally&lt;/strong&gt; adds a backlink to the PR to the original user issue (using Claude&amp;#39;s &lt;a href=&quot;https://code.claude.com/docs/en/agent-sdk/user-input&quot;&gt;&lt;code&gt;AskUserQuestion&lt;/code&gt; tool&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that we only ask the LLM to &lt;em&gt;attempt&lt;/em&gt; a reproduction and stop if too complicated. This sort of logic is very effective when working with agents since if we ask too much of them, they will often stumble. If we give them an out, they&amp;#39;re more likely to explain the challenge than just stumble through it.&lt;/p&gt;
&lt;h3&gt;Example run on the Python issue&lt;/h3&gt;
&lt;p&gt;Continuing with the above Python example, the skill created &lt;a href=&quot;https://github.com/getsentry/repro/pull/41&quot;&gt;this reproduction&lt;/a&gt;. We can see that it created a minimal Django app and gave very clear instructions to run the reproduction. Using this basic setup, I was able to roll out &lt;a href=&quot;https://github.com/getsentry/sentry-python/pull/5963&quot;&gt;the subsequent fix&lt;/a&gt; very rapidly. I probably saved a few hours of figuring out how to setup Django with an HTTPS proxy correctly and then examining how that interacts with our SDK logic.&lt;/p&gt;
&lt;h2&gt;Lessons on writing skills&lt;/h2&gt;
&lt;p&gt;Skills are very generic Markdown files so it&amp;#39;s a bit opaque how to make them reliable and avoid having them go off the rails.&lt;/p&gt;
&lt;p&gt;Some insights I have from writing this one:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use CLIs to interact with other systems; here we&amp;#39;re using the &lt;a href=&quot;https://github.com/getsentry/repro/blob/e7bbd6e958bbe4e7ab070c0142e2c4dc4fa938ad/.claude/skills/repro/SKILL.md?plain=1#L31-L35&quot;&gt;&lt;code&gt;gh&lt;/code&gt; CLI&lt;/a&gt; to perform GitHub operations&lt;/li&gt;
&lt;li&gt;Split out the work to be done into clear steps&lt;/li&gt;
&lt;li&gt;Add an &lt;a href=&quot;https://github.com/getsentry/repro/blob/e7bbd6e958bbe4e7ab070c0142e2c4dc4fa938ad/.claude/skills/repro/SKILL.md?plain=1#L165-L169&quot;&gt;&lt;code&gt;Error Handling&lt;/code&gt;&lt;/a&gt; section explaining what&amp;#39;s not allowed and what to do with bad inputs&lt;/li&gt;
&lt;li&gt;Use other in-built tools such as &lt;code&gt;AskUserQuestion&lt;/code&gt; for user input or validation&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Full automation?&lt;/h2&gt;
&lt;p&gt;We will play around with fully automating this flow on GitHub issues in the future. A major concern voiced by several engineers here is increased bot noise. We&amp;#39;re already drowning in bot communication on several fronts so we want to be careful how many of these we enable automatically. The right amount of automation in any given problem space is not always full automation and a pair of human eyes in the right places are absolutely necessary.&lt;/p&gt;
</content:encoded></item><item><title>Errors, traces, logs, metrics: when to reach for what</title><link>https://blog.sentry.io/errors-traces-logs-metrics-when-to-reach-for-what/</link><guid isPermaLink="true">https://blog.sentry.io/errors-traces-logs-metrics-when-to-reach-for-what/</guid><description>Errors, traces, logs, and metrics overlap enough that it&apos;s hard to know which to use. Here&apos;s when to reach for each signal, with a real debugging walkthrough.</description><pubDate>Fri, 05 Jun 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When should I reach for a log, a trace, or a metric? I hit that question constantly when I instrument code, and I watch coding agents hit it too. It sounds like it should be obvious. Errors, traces, logs, and metrics are the four kinds of telemetry most apps run on, four tools in one box, and they overlap enough that the honest answer is every developer&amp;#39;s favourite: &lt;em&gt;it depends&lt;/em&gt;. You can stuff context into span attributes instead of logging it. You can count log events instead of emitting a metric. You can add a duration to a log and call it a span.&lt;/p&gt;
&lt;p&gt;[I had a spiderman meme here but legal told me it would be infringing so I removed it]&lt;/p&gt;
&lt;p&gt;But the fact that you &lt;em&gt;can&lt;/em&gt; doesn&amp;#39;t mean you &lt;em&gt;should&lt;/em&gt;. Each signal exists because it answers a different question, and feeds a different workflow once it lands. Left without solid guidelines, the default is to reach for whatever&amp;#39;s most familiar or already there, and miss what the other kinds are for.&lt;/p&gt;
&lt;p&gt;This post is the guidance I wanted to have, for myself and my robots. Want just the skill? &lt;a href=&quot;#getting-started&quot;&gt;Skip to the end&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In Sentry, errors, traces, logs, and metrics all come from one SDK, included on every plan. Errors and &lt;a href=&quot;https://sentry.io/product/tracing/&quot;&gt;tracing&lt;/a&gt; have been around for years (&lt;a href=&quot;https://blog.sentry.io/the-story-of-sentry/&quot;&gt;2012&lt;/a&gt; and &lt;a href=&quot;https://blog.sentry.io/see-slow-faster-with-performance-monitoring/&quot;&gt;2020&lt;/a&gt;), &lt;a href=&quot;https://sentry.io/product/logs/&quot;&gt;structured logs landed last year&lt;/a&gt;, and &lt;a href=&quot;https://sentry.io/product/metrics/&quot;&gt;Application Metrics&lt;/a&gt; completed the set back in May of this year. If you&amp;#39;ve had your application instrumented with Sentry for a while, errors and traces are probably already flowing, with logs and metrics left as tools for you to complete your telemetry story.&lt;/p&gt;
&lt;h2&gt;Errors, traces, logs, metrics: one question each&lt;/h2&gt;
&lt;h4&gt;&lt;a href=&quot;https://docs.sentry.io/product/issues/&quot;&gt;Errors&lt;/a&gt;: &amp;quot;What just broke?&amp;quot;&lt;/h4&gt;
&lt;p&gt;A stack trace and an exception type, grouped into an Issue that gets deduplicated, assigned, and tracked until it&amp;#39;s resolved. If your code threw an exception, it&amp;#39;s an error.&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://docs.sentry.io/product/trace-explorer/&quot;&gt;Traces&lt;/a&gt;: &amp;quot;Did the request flow the way it was supposed to?&amp;quot;&lt;/h4&gt;
&lt;p&gt;A trace is a waterfall of timed spans. It&amp;#39;s how you follow a request across your services and see where the time went: the DB query that dragged, the API call that timed out, the LLM tool call that took 8 seconds instead of 200ms.&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://docs.sentry.io/product/metrics/&quot;&gt;Metrics&lt;/a&gt;: &amp;quot;How&amp;#39;s this trending over time?&amp;quot;&lt;/h4&gt;
&lt;p&gt;Counters, gauges, and distributions, each kept as an individual measurement you can slice by any attribute and drill from an aggregate back into the samples (and the trace) behind it. Not just &amp;quot;12,000 checkouts this week,&amp;quot; but 8,400 from the US, 2,600 from the EU, and 1,000 from everywhere else, and how that line moved across the last deploy. Metrics are a historical signal as much as a right-now one, which makes them an easy candidate for dashboards and alerts (but you can still set up alerts on pretty much all signals from Sentry).&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://docs.sentry.io/product/logs/&quot;&gt;Logs&lt;/a&gt;: &amp;quot;What was happening at this point in the code?&amp;quot;&lt;/h4&gt;
&lt;p&gt;The state of the system at one specific moment, captured as a structured event: config values, feature flags, the inputs and outputs of a function, the user ID. Logs are the trail through a function&amp;#39;s decision tree: the markers you drop at the points where the code makes a choice, so that later, a human or an agent can follow the reasoning. They fill in the &lt;em&gt;why&lt;/em&gt; once errors and traces have told you what broke and where the time went.&lt;/p&gt;
&lt;h2&gt;A real(ish) world example&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s say you run a storefront with a React frontend and a Python API. Support starts forwarding tickets: the product recommendations on the account page look generic for a chunk of logged-in customers: bestsellers, not the personalized picks they&amp;#39;re used to. The vibes are off.&lt;/p&gt;
&lt;h3&gt;Did anything crash?&lt;/h3&gt;
&lt;p&gt;First place I&amp;#39;d look is Issues. No exception in the React app, no failed request, every call to &lt;code&gt;/recommendations/{user_id}&lt;/code&gt; came back 200. As far as error tracking is concerned, the app is perfectly healthy.&lt;/p&gt;
&lt;h3&gt;Was anything slow, or did the request go off-path?&lt;/h3&gt;
&lt;p&gt;Pull a trace for one of the affected requests. The route and the database queries are auto-instrumented; I added a few &lt;a href=&quot;https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation/#add-spans-to-a-transaction&quot;&gt;named spans&lt;/a&gt; for the recommendation steps:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/images/posts/errors-traces-logs-metrics-when-to-reach-for-what/recommendations-trace-waterfall.png&quot; alt=&quot;An affected request&apos;s trace in Sentry: an http.server span for the GET /recommendations route over child spans for the user lookup, the ranking_v2 flag check, the empty recommendations_v2 query, the fallback to popular items, and ranking.&quot;&gt;&lt;/p&gt;
&lt;p&gt;The request loaded the user, evaluated the &lt;code&gt;ranking_v2&lt;/code&gt; flag, queried &lt;code&gt;recommendations_v2&lt;/code&gt;, fell back to popular items, and ranked them. The path is right and the timing&amp;#39;s fine. That &lt;code&gt;recommendations_v2&lt;/code&gt; query &lt;em&gt;succeeded&lt;/em&gt; (returning zero rows is a perfectly successful query), so the code did what it was built to do and fell back. The trace tells me the request flowed as designed. It can&amp;#39;t tell me the design just quietly failed this user. On the surface, everything is fine.&lt;/p&gt;
&lt;h3&gt;Can we dig a little deeper?&lt;/h3&gt;
&lt;p&gt;Search the logs for the user from the ticket, and the structured log from inside the handler will give you the state at the moment it decided to fall back.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/images/posts/errors-traces-logs-metrics-when-to-reach-for-what/recommendations-logs-user-search.png&quot; alt=&quot;The recommendations lookup log for user.id 124 in Sentry, expanded to show its attributes: the ranking_v2 flag is on, source_table is recommendations_v2, candidate_count is 0, and outcome is fallback.&quot;&gt;&lt;/p&gt;
&lt;p&gt;This user got bucketed into the &lt;code&gt;ranking_v2&lt;/code&gt; feature flag, which reads personalized picks from a new &lt;code&gt;recommendations_v2&lt;/code&gt; table. The table shipped, but the rows were never backfilled, so the lookup came back empty. To the code, an empty result is a perfectly valid &amp;quot;no personalized recs for this user,&amp;quot; the same thing a brand-new user with no history would get. So it falls back to bestsellers and returns 200.&lt;/p&gt;
&lt;p&gt;Why not just attach this data on the span? You could set &lt;code&gt;outcome&lt;/code&gt; and &lt;code&gt;candidate_count&lt;/code&gt; as span attributes. But traces might be sampled, and the one request a customer is complaining about &lt;em&gt;usually&lt;/em&gt; ends up being the one that&amp;#39;s sampled out (at least with my luck). A span attribute is great for reading a trace you&amp;#39;ve found; it can&amp;#39;t help you find one. Logs aren&amp;#39;t sampled.&lt;/p&gt;
&lt;h3&gt;How many people hit it?&lt;/h3&gt;
&lt;p&gt;One affected customer is a support ticket. Knowing whether it&amp;#39;s a small subset of users or a significant chunk is the difference between fixing it Monday and paging someone tonight. A &lt;code&gt;recommendations.served&lt;/code&gt; counter, tagged with &lt;code&gt;ranking_version&lt;/code&gt; and &lt;code&gt;outcome&lt;/code&gt;, draws the line:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/images/posts/errors-traces-logs-metrics-when-to-reach-for-what/recommendations-metric-rate.png&quot; alt=&quot;Sentry&apos;s Application Metrics explorer showing the recommendations.served counter with two queries (one filtered to outcome:personalized, one for the total) and an equation A / B * 100 grouped by ranking_version, producing a personalized rate of 97.9% for v1 and 3.3% for v2.&quot;&gt;&lt;/p&gt;
&lt;p&gt;The v2 path is serving almost nothing but fallbacks, v1 is normal, and the drop lines up with the flag rollout. Scope and trigger, without opening a single trace.&lt;/p&gt;
&lt;p&gt;No one signal cracked it; each ruled something out. No Issues in the feed meant it wasn&amp;#39;t a crash. The metric said it wasn&amp;#39;t a one-off: the whole &lt;code&gt;v2&lt;/code&gt; cohort was falling back. The trace, where one was sampled, showed the path running exactly as designed, which is why it slipped through. The log, pulled up by the &lt;code&gt;user_id&lt;/code&gt; from the ticket, said &lt;em&gt;why&lt;/em&gt;, and I never needed the trace to get to it.&lt;/p&gt;
&lt;h2&gt;When to reach for what&lt;/h2&gt;
&lt;p&gt;I use this as a gut check:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you want to know&lt;/th&gt;
&lt;th&gt;Reach for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Something crashed, show the stack trace&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Errors&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How long did this take? Which step is slow?&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Traces&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Did the request flow through the steps I expected?&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Traces&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What was the state when the code made this decision?&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Logs&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What did this function receive and return?&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Logs&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How often does X happen? Is the rate normal?&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Metrics&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Did something change after the deploy?&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Metrics&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The tricky cases are the overlaps, and of course there is nuance to all of this because the same value can show up in more than one signal.&lt;/p&gt;
&lt;h4&gt;Span attribute or metric?&lt;/h4&gt;
&lt;p&gt;If it&amp;#39;s context about &lt;em&gt;one request&amp;#39;s flow through the system&lt;/em&gt; and you want it while reading that trace, it&amp;#39;s a span attribute. It rides on the span in the waterfall. If it&amp;#39;s a standalone value you want to chart, alert on, or slice over time across &lt;em&gt;all&lt;/em&gt; requests, it&amp;#39;s a metric. The same number can warrant both: &lt;code&gt;candidate_count&lt;/code&gt; as a span attribute lets me read one request; &lt;code&gt;recommendations.served&lt;/code&gt; as a metric lets me watch the rate. One is for inspecting a single flow, the other for watching the aggregate.&lt;/p&gt;
&lt;h4&gt;Log or span?&lt;/h4&gt;
&lt;p&gt;The span is the timed node in the flow, and most of them are auto-instrumented, so you rarely write them. The log is the decision-point state &lt;em&gt;inside&lt;/em&gt; that node, and you always write it on purpose. Span answers &lt;em&gt;where&lt;/em&gt; and &lt;em&gt;how long&lt;/em&gt;; log answers &lt;em&gt;what was true and why&lt;/em&gt;.&lt;/p&gt;
&lt;h4&gt;Log or metric?&lt;/h4&gt;
&lt;p&gt;A log is one request&amp;#39;s story, the needle. A metric is the aggregate, the question of whether the haystack is normal. When you want to find the specific request that went wrong, that&amp;#39;s a log. When you want to know how many requests went wrong, that&amp;#39;s a metric.&lt;/p&gt;
&lt;h4&gt;Error or log?&lt;/h4&gt;
&lt;p&gt;If it needs a stack trace and should be tracked as an Issue, it&amp;#39;s an error. If it&amp;#39;s an unexpected-but-handled condition worth recording, it&amp;#39;s a log. If it&amp;#39;s truly non-critical, &lt;code&gt;logger.warning(exc_info=True)&lt;/code&gt; captures the traceback in logs without creating noise in your error feed.&lt;/p&gt;
&lt;h2&gt;What the instrumentation looks like&lt;/h2&gt;
&lt;p&gt;Everything above came out of one endpoint: the &lt;code&gt;GET /recommendations/{user_id}&lt;/code&gt; route from the walkthrough, the function that loads the user, checks the &lt;code&gt;ranking_v2&lt;/code&gt; flag, queries &lt;code&gt;recommendations_v2&lt;/code&gt;, and falls back to popular items when it comes back empty. Here&amp;#39;s that same handler with the instrumentation in place.&lt;/p&gt;
&lt;p&gt;Most of it you don&amp;#39;t write. The FastAPI integration traces the request, the database integration traces every query, so you get the path and the timing without a single hand-written span.&lt;/p&gt;
&lt;p&gt;What you do place by hand are the deliberate signals: a span attribute or two to enrich the flow, the decision-point log, and the metric.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
from sentry_sdk import logger

# The route is auto-instrumented. FastAPI gives you the request span;
# the DB integration gives you a span for every query below. You write none of it.
@app.get(&amp;quot;/recommendations/{user_id}&amp;quot;)
def get_recommendations(user_id: int):
    user = db.get_user(user_id)                          # auto-instrumented db span
    use_v2 = flag_enabled(&amp;quot;ranking_v2&amp;quot;, user)
    ranking_version = &amp;quot;v2&amp;quot; if use_v2 else &amp;quot;v1&amp;quot;

    candidates = db.personalized_recs(user_id, version=ranking_version)  # auto db span
    outcome = &amp;quot;personalized&amp;quot; if candidates else &amp;quot;fallback&amp;quot;
    items = candidates or db.popular_items()             # auto db span on the fallback

    # SPAN ATTRIBUTE: context about THIS request&amp;#39;s flow, read inside the trace.
    # It rides on the auto-instrumented request span; no new span needed.
    span = sentry_sdk.get_current_span()
    span.set_data(&amp;quot;ranking_version&amp;quot;, ranking_version)
    span.set_data(&amp;quot;recommendation.outcome&amp;quot;, outcome)

    # LOG: the trail through the decision tree, the state at the moment the
    # code chose personalized vs. fallback. The only signal that records *why*.
    logger.info(
        &amp;quot;recommendations lookup&amp;quot;,
        attributes={
            &amp;quot;user_id&amp;quot;: user_id,
            &amp;quot;ranking_version&amp;quot;: ranking_version,
            &amp;quot;flag.ranking_v2&amp;quot;: use_v2,
            &amp;quot;source_table&amp;quot;: f&amp;quot;recommendations_{ranking_version}&amp;quot;,
            &amp;quot;candidate_count&amp;quot;: len(candidates),
            &amp;quot;outcome&amp;quot;: outcome,
        },
    )

    # METRIC: the rate across all requests, sliceable by version and outcome.
    sentry_sdk.metrics.count(
        &amp;quot;recommendations.served&amp;quot;,
        1,
        attributes={&amp;quot;ranking_version&amp;quot;: ranking_version, &amp;quot;outcome&amp;quot;: outcome},
    )

    return items
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three deliberate touches, each carrying a piece the others can&amp;#39;t. The span attribute tags the request&amp;#39;s flow with the ranking path so it&amp;#39;s right there when I open the trace. The log records what the function decided and why, at the instant it decided. The metric counts the outcome with enough dimension to slice it later.&lt;/p&gt;
&lt;p&gt;If you &lt;em&gt;do&lt;/em&gt; want a sub-operation timed in the waterfall (say the ranking step, or a call to an external recommender), you can wrap it in a custom span with &lt;a href=&quot;https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation/&quot;&gt;&lt;code&gt;sentry_sdk.start_span&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Beyond what you write, the SDK fills in even more on its own. Frontend SDKs tag everything with the browser, OS, and release. Call &lt;code&gt;sentry_sdk.set_user()&lt;/code&gt; once and that user follows the errors, spans, logs, and metrics for the request. And because all four come from the same SDK, they share a &lt;code&gt;trace_id&lt;/code&gt; and correlate on their own: every log carries the trace it belongs to, and you can jump from a metric spike straight into the traces behind it, without gluing four vendors together to get there.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/images/posts/errors-traces-logs-metrics-when-to-reach-for-what/trace-connected-waterfall.png&quot; alt=&quot;Sentry trace view for the GET /recommendations route: the http.server route span and the database query spans are auto-instrumented, alongside a few custom spans for the recommendation steps, with Waterfall, Logs, and Application Metrics tabs all hanging off the same trace.&quot;&gt;&lt;/p&gt;
&lt;p&gt;All of this is ready for you to use and included in every plan. The deliberate signals (the span attributes, the decision-point logs, the metrics) are the ones you place yourself, and they only help if you do it ahead of time, at the spots where your code makes a decision worth questioning later.&lt;/p&gt;
&lt;h2&gt;Right tool for the job&lt;/h2&gt;
&lt;p&gt;The split above isn&amp;#39;t just conceptual. It&amp;#39;s baked into the APIs, and each one is tuned for its job. The &lt;strong&gt;Metrics API&lt;/strong&gt; is built for emitting counts and measures you&amp;#39;ll aggregate. The &lt;strong&gt;span API&lt;/strong&gt; is built for measuring durations and the shape of a request. The &lt;strong&gt;log API&lt;/strong&gt; integrates with your favourite &lt;a href=&quot;https://sentry.io/product/logs/&quot;&gt;structured logging&lt;/a&gt; library, so the lines you already write become queryable events. Reaching for the API that matches the workflow usually means reaching for the one that matches the &lt;em&gt;kind&lt;/em&gt; of value you have: a count, a duration, or a moment.&lt;/p&gt;
&lt;p&gt;Sampling falls out of the same logic. Traces are best as a &lt;a href=&quot;https://docs.sentry.io/platforms/python/tracing/configure-sampling/&quot;&gt;&lt;em&gt;sampled representation&lt;/em&gt;&lt;/a&gt; of your traffic: you don&amp;#39;t need every request to understand where time goes, so a percentage is plenty (and cheaper). Logs are the opposite: you keep all of them, because the entire point is to find the one rare request that went sideways, and you can&amp;#39;t find what you sampled away. Metrics aren&amp;#39;t sampled either; like logs, you filter them with &lt;a href=&quot;https://docs.sentry.io/platforms/python/metrics/#before_send_metric&quot;&gt;&lt;code&gt;before_send_metric&lt;/code&gt;&lt;/a&gt;. Match the retention to the question: a representative sample for &amp;quot;where does time go,&amp;quot; every single event for &amp;quot;what happened to &lt;em&gt;this&lt;/em&gt; request.&amp;quot;&lt;/p&gt;
&lt;h2&gt;You&amp;#39;re not the only one debugging your codebase anymore&lt;/h2&gt;
&lt;p&gt;Cody from &lt;a href=&quot;https://modem.dev/&quot;&gt;Modem&lt;/a&gt; instrumented his AI agent to find out where it was spending time. He worked with Codex to wrap the async work and the logical chunks (everything that runs before the call to the model, say) in spans. Cache hits and time-to-first-token became metrics he could watch over time. Values that only meant something next to a specific operation stayed as span attributes, and the lightweight &amp;quot;this happened here&amp;quot; markers became logs. The span-attribute-versus-metric call wasn&amp;#39;t always obvious to him; his rule was that if a value only made sense in the context of a span, it lived on the span.&lt;/p&gt;
&lt;p&gt;With the tracing in place, he pointed Codex at the Sentry data through the MCP server, feeding it real runs from his Playwright tests in development, and gave it one goal: optimize the code path. The agent read the spans, found work that could run in parallel, and rewrote the code to stop awaiting results until they were actually needed.&lt;/p&gt;
&lt;p&gt;It could do that because a trace is a structured dependency tree with timing on every node, a format an agent can reason about directly. Hand it the same information as a stream of log lines and it would have to reconstruct the call graph from timestamps and string matching first.&lt;/p&gt;
&lt;h2&gt;But what about wide events?&lt;/h2&gt;
&lt;p&gt;There&amp;#39;s a popular argument that the four signals are overkill: emit one rich, wide event per request and derive the rest later. It&amp;#39;s half right.&lt;/p&gt;
&lt;p&gt;Emit wide, absolutely. The best version of any signal is a structured event packed with context (the flag that was on, the user, the inputs and the outputs), not a bare number or a one-line string.&lt;/p&gt;
&lt;p&gt;But the shape you emit is the shape you get to work with. One fat event in a columnar store charts fine after the fact, but it can&amp;#39;t group itself into a deduplicated Issue, render itself as a waterfall, or fire a real-time alert on a threshold you haven&amp;#39;t defined yet. Those are workflows, and each needs its data in a particular shape.&lt;/p&gt;
&lt;p&gt;So emit wide, into the signal whose workflow you actually need. That&amp;#39;s why the handler emits both a metric and a log: same decision, same trace, two shapes, because watching a rate and reconstructing one request are different jobs.&lt;/p&gt;
&lt;h2&gt;Getting started&lt;/h2&gt;
&lt;p&gt;Logs and metrics are the two you probably haven&amp;#39;t turned on yet — they’re relatively new to Sentry, and people are still just finding them. Both are included on every plan.&lt;/p&gt;
&lt;p&gt;You don&amp;#39;t have to wire them up by hand. Point your coding agent at &lt;a href=&quot;https://skills.sentry.dev/&quot;&gt;Sentry&amp;#39;s setup skills&lt;/a&gt; for your stack and it installs the SDK, turns on tracing, logs, and metrics, and drops instrumentation at the decision points. Then aim it at your Sentry data through the &lt;a href=&quot;https://mcp.sentry.dev/&quot;&gt;MCP server&lt;/a&gt; and give it something real: your slowest trace, your newest issue.&lt;/p&gt;
&lt;p&gt;Prefer to grab just the decision framework? It&amp;#39;s a skill of its own:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx skills add getsentry/sentry-for-ai --skill sentry-instrumentation-guide
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The telemetry you emit to debug is the same telemetry it reads to help.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Want to go deeper on the logging side? Ben&amp;#39;s follow-up post, &lt;a href=&quot;https://blog.sentry.io/logging-best-practices/&quot;&gt;When and what should I be logging?&lt;/a&gt;, covers what to log, how to structure your messages, and what to avoid.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>How we cut build times by two-thirds by deleting our CMS</title><link>https://blog.sentry.io/cut-build-times-delete-cms/</link><guid isPermaLink="true">https://blog.sentry.io/cut-build-times-delete-cms/</guid><description>Sentry replaced its CMS with Astro, Markdown, and Claude Code skills — cutting build times from 14 to under 4 minutes and eliminating API failures.</description><pubDate>Thu, 28 May 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At Sentry, we&amp;#39;re obsessed with things not breaking. It&amp;#39;s kind of our whole deal. But for a while, our own marketing site was testing that obsession.&lt;/p&gt;
&lt;p&gt;Much of what you see on &lt;a href=&quot;https://sentry.io&quot;&gt;sentry.io&lt;/a&gt; (the marketing site, blog, open source microsite, etc.) were running on a fleet of legacy Gatsby sites powered by a traditional headless CMS. On paper, it worked. In practice, we were juggling a fragile web of plugins, restrictive schemas, and external API dependencies that loved to fail right when we needed to ship.&lt;/p&gt;
&lt;p&gt;So, we did what any sane engineering team would do: we ripped it out and replaced it with Astro, Markdown, and AI-driven automation.&lt;/p&gt;
&lt;h2&gt;The problem: the &amp;quot;headless&amp;quot; headache&lt;/h2&gt;
&lt;p&gt;Our old stack was starting to feel like a Rube Goldberg machine.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The build bottleneck:&lt;/strong&gt; Gatsby&amp;#39;s consolidated data layer was convenient, but as our content grew, our build times ballooned to ~14 minutes/build. At an average of 95 builds/day, this ended up being around 22 build hours used daily.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The CMS tax:&lt;/strong&gt; We managed the content of ~2500 pages in a single CMS instance. We have different page schemas and connected component schemas without the ability to have conditional fields, so we ended up buying a conditional fields plugin to avoid hitting the schema limit. This made for an additional annual subscription on top of our monthly subscription, and scalability was still limited.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;External fragility:&lt;/strong&gt; Every build relied on external CMS and marketing automation system APIs via Gatsby plugins. During the last month before we started the rebuild, an issue with the CMS&amp;#39;s Gatsby plugin would fail 3-5 times a day (to which there was no resolution, even after submitting a support ticket) and the marketing automation API would also fail multiple times a day due to rate limits (more on that below).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The solution: Astro and the power of &amp;quot;just files&amp;quot;&lt;/h2&gt;
&lt;p&gt;We migrated the framework to Astro. We chose it because it&amp;#39;s built for the modern web — fast by default and incredibly flexible.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vite-powered speed:&lt;/strong&gt; Moving to Vite meant our local development and production builds finally felt like they belonged in 2026. We reduced our build times from ~14 minutes to less than 4 minutes, resulting in a savings of ~15.8 build hours daily.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Framework agnostic:&lt;/strong&gt; Astro lets us use the best tool for the job. If a component works better in React, we use React. If it&amp;#39;s a simple static partial, it&amp;#39;s just HTML/CSS.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vercel for the heavy lifting:&lt;/strong&gt; We offloaded image processing to Vercel, ensuring our assets are optimized without dragging down the build process.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But the biggest shift wasn&amp;#39;t the framework — it was how we handled content. We ditched the headless CMS UI for Markdown and Frontmatter.&lt;/p&gt;
&lt;h2&gt;AI-native content management (without the SaaS bloat)&lt;/h2&gt;
&lt;p&gt;Instead of paying for an &amp;quot;AI Add-on&amp;quot; from a CMS provider, we built a direct integration with Claude Skills.&lt;/p&gt;
&lt;p&gt;Now, when someone needs to update the site, they don&amp;#39;t log into a bloated dashboard. They use a skill-driven workflow that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Guides the user through a process that precisely updates the Markdown files and Frontmatter directly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generates a live preview.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Drafts a Pull Request for review.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Why go custom instead of using a CMS-integrated AI?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zero dependencies:&lt;/strong&gt; Content lives in the repo. No API outages mean no failed builds.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unlimited schemas:&lt;/strong&gt; With Frontmatter, we define the structure. If we need a new field or schema type, we just add it. No subscription tiers, no restrictions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The &amp;quot;Sentry&amp;quot; way:&lt;/strong&gt; For a company with a developer-first culture that values the deeply technical, managing content as code feels right. It&amp;#39;s version-controlled, peer-reviewed, and lives right next to the components that render it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The process: how we did it&lt;/h2&gt;
&lt;p&gt;Moving a site with ~2500 pages between our marketing site and blog is a massive undertaking. We had a team of 2.5 developers and a two-month window to get it done.&lt;/p&gt;
&lt;p&gt;Because of the small team size and large site volume, we relied on Claude Code for much of the coding. Our developers spent the bulk of their time on planning, scoping, and developing requirements, then reviewing the code, directing changes, and fine-tuning the output.&lt;/p&gt;
&lt;h3&gt;Scoping&lt;/h3&gt;
&lt;p&gt;This was easier for this project for 2 reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We use a monorepo for these websites so the bots had the full context of what was being built and migrated&lt;/li&gt;
&lt;li&gt;We did not implement any net-new design&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since we&amp;#39;ve been working in the existing codebase for over a few years, we had some ideas of where we could easily remove code bloat. We took a deeper look at the pages in certain directories to validate, and based on the content, eliminate bespoke pages with templates. As a result, we consolidated ~200 pages into 3 templates, making the site DRY and significantly easier to maintain.&lt;/p&gt;
&lt;h3&gt;Building with bots&lt;/h3&gt;
&lt;p&gt;We started with the data. Since our headless CMS was plugged into our Gatsby site and pre-existing parts of the Astro site, and our headless CMS provided JSON files of each schema available, we provided the planning agent with existing CMS schema and had it duplicated in Frontmatter. Since we were already connected to the CMS&amp;#39;s API, we had an agent swarm pull down the data, map it to Frontmatter files, and pull down the images and save them locally as either an &lt;code&gt;asset&lt;/code&gt; (for all non-meta image images needing image optimization) or in the &lt;code&gt;public&lt;/code&gt; directory for SEO images.&lt;/p&gt;
&lt;p&gt;Once the data was in place, we provided plan agents with the location of the existing template files in Gatsby, directions on where to place it in the new framework, what data it was using, and asked the agent to interview us on any missing information. From there, the planning agent would pass along the build tests to general purpose agents for the build, which was passed on to general purpose agents for testing.&lt;/p&gt;
&lt;p&gt;After that round, our team would review &amp;amp; fix any regressions, which was followed with AI PR reviews using both Sentry&amp;#39;s &lt;a href=&quot;https://sentry.io/product/seer/ai-code-review/&quot;&gt;Seer AI code review tool&lt;/a&gt; and Cursor&amp;#39;s Bugbot (along with other quality checks built into the repo, including secret scanning and our standard automated tests).&lt;/p&gt;
&lt;h3&gt;Testing with bots&lt;/h3&gt;
&lt;p&gt;As part of our development process, we experimented with Claude running visual regression tests with Playwright and a homegrown MCP we built to compare visual elements from our Gatsby site to the new Astro replacement.&lt;/p&gt;
&lt;h4&gt;The DOM-inspector MCP&lt;/h4&gt;
&lt;p&gt;Big shoutout to Dylan Coots on our team who built a DOM Inspector MCP that uses Puppeteer (headless Chrome) to connect to a locally running dev server and programmatically inspect, measure, and interact with elements on the page. It was designed to find UI layout issues like spacing shifts, element dimensions, and computed styles that can be passed along to a bot to fix.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Core Architecture&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;DOMInspector&lt;/code&gt; class&lt;/strong&gt; — the central object that owns the Puppeteer browser/page lifecycle. It has two modes: a fully-owned browser (launched by the class) and a session-managed mode where an external page is passed in via the static &lt;code&gt;fromPage()&lt;/code&gt; factory method.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Browser management&lt;/strong&gt; — launches a headless Chrome instance with memory-constrained flags (&lt;code&gt;--max-old-space-size=256&lt;/code&gt;, limited renderer processes) and handles graceful shutdown with a 5-second timeout before force-killing the process by PID if needed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DOM inspection methods&lt;/strong&gt; — includes &lt;code&gt;inspectElement()&lt;/code&gt; (dimensions + computed CSS), &lt;code&gt;measureDistance()&lt;/code&gt; (pixel/rem gap between two elements including which CSS property creates it), &lt;code&gt;measureLayoutShift()&lt;/code&gt; (reloads the page and diffs element positions before/after a transition), and &lt;code&gt;debugPage()&lt;/code&gt; (scans the DOM for common component patterns when a selector isn&amp;#39;t found).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Interaction &amp;amp; navigation&lt;/strong&gt; — &lt;code&gt;interactWithElement()&lt;/code&gt; clicks a selector and measures before/after state; &lt;code&gt;navigateToUrl()&lt;/code&gt; navigates to a new URL and clears stale console logs; &lt;code&gt;setViewport()&lt;/code&gt; supports named presets (mobile/tablet/desktop/large) or any custom pixel width with auto-calculated height.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Utility methods&lt;/strong&gt; — &lt;code&gt;screenshot()&lt;/code&gt; (full page or scoped to a selector, returned as base64), &lt;code&gt;evaluateJs()&lt;/code&gt; (runs arbitrary async JS in the page context), &lt;code&gt;waitForSelector()&lt;/code&gt;, &lt;code&gt;getPageContent()&lt;/code&gt; (text/HTML/outerHTML with truncation safety), and &lt;code&gt;getConsoleLogs()&lt;/code&gt; (buffered, capped at 500 entries).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CLI entrypoint&lt;/strong&gt; — &lt;code&gt;main()&lt;/code&gt; only runs when the file is executed directly (not &lt;code&gt;require()&lt;/code&gt;&amp;#39;d), accepts &lt;code&gt;--url&lt;/code&gt; and &lt;code&gt;--port&lt;/code&gt; flags, and runs a quick inspection of a hardcoded component (&lt;code&gt;.WhoSentYouWrapper&lt;/code&gt;) as a smoke test.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;extractUrlFromText()&lt;/code&gt;&lt;/strong&gt; — a helper exported alongside the class for parsing localhost URLs out of natural-language strings, suggesting this is meant to be called from an MCP server that receives user text prompts.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;What worked for us&lt;/h4&gt;
&lt;p&gt;The Playwright visual regression tests and the DOM Inspector MCP worked best together. Our visual testing workflow started with Playwright tests for each template to identify visual regressions. The results would be passed to another agent and fixes made. We followed it with the DOM Inspector MCP to fine tune elements that weren&amp;#39;t fixed after the Playwright test fixes. We found the DOM Inspector to be more accurate with smaller, element-based inspections. Even then it wasn&amp;#39;t 100%, but it did save us time on fixing tedious styling issues.&lt;/p&gt;
&lt;h3&gt;Updating content (also with bots)&lt;/h3&gt;
&lt;p&gt;Since updating content hurts in the CMS, we wanted to make it easy to update content without needing deep technical knowledge of Frontmatter or code in general, so we made some Claude skills for it.&lt;/p&gt;
&lt;h4&gt;Skills for the command line&lt;/h4&gt;
&lt;p&gt;For non-developers, understanding git operations (or even being in the terminal) can be intimidating. But, we saw the value of using a PR-based workflow for quality and consistency. So, we made some utility skills to help with this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/new-branch&lt;/code&gt; — this would pull down the main branch on origin to prevent any avoidable merge conflicts, add a prefix to the branch name to know it came from a skill, and avoid any cruft from past branch checkouts from being included in the new PR.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/deploy-local-preview&lt;/code&gt; — since starting up a local dev server to preview your work takes a few lines in the terminal, we created a skill that does this for users. The skill will navigate to the site selected, spin up a dev server, and deploy a local preview.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Skills to update content&lt;/h4&gt;
&lt;p&gt;For each of our page types, we built skills that will create a Frontmatter file, ask the user for each field, upload images (with image size checks), call &lt;code&gt;/deploy-local-preview&lt;/code&gt; to check the work, and use the Github CLI to create a pull request. This provides guardrails to make sure all the required information is given, reduces navigating a cumbersome CMS UI, prevents massive image files from being used (we added a polite reminder to compress to under 250kb and won&amp;#39;t accept the larger file) and keeps page updates strictly to focus on content, not code.&lt;/p&gt;
&lt;h4&gt;Things to consider&lt;/h4&gt;
&lt;p&gt;Since we&amp;#39;ve built out skills with AI to update the content, there are a few things to keep in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Compute is expensive.&lt;/strong&gt; You don&amp;#39;t need Opus to deploy a local preview when Haiku will do the job. We set default models on certain skills to make sure the model is right for the task. We also set the default model in the repo to Opus 4.6 to save on usage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use skills to catch large image files and other common things that will slow down performance.&lt;/strong&gt; We added filesize limits to our page skills to prevent massive images from getting uploaded to our codebase and slowing down the build and site performance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Protect sensitive parts of the site with Hooks.&lt;/strong&gt; Since the models have access to the whole codebase and there are sensitive items you don&amp;#39;t want changed, don&amp;#39;t allow AI to change them. For example, we protected our Content Security Policy with a &lt;code&gt;PreToolUse&lt;/code&gt; hook that prevents any changes to our CSP.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Fixing the &amp;quot;rate limit problem&amp;quot;&lt;/h2&gt;
&lt;p&gt;While we were under the hood, we tackled another recurring nightmare: API rate limits for our forms.&lt;/p&gt;
&lt;p&gt;Our forms relied on fetching fields from our marketing automation system during the build. If we hit a rate limit, the build broke. To fix this, we built a service using Vercel Blob. We now fetch and store form fields in a fast, reliable blob store at the start of the build.&lt;/p&gt;
&lt;p&gt;This reduced our marketing automation system API calls to nearly zero during the critical build phase, removing yet another point of failure.&lt;/p&gt;
&lt;h2&gt;The results: reliability as a feature&lt;/h2&gt;
&lt;p&gt;The shift from a heavy, API-dependent CMS to a lean, file-based Astro site has been a game-changer for our productivity.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;Metric&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;Before (Gatsby + CMS)&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;After (Astro + Claude)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Average Build Time&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;14 Minutes&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&amp;lt; 4 Minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Web Vitals Score&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;89&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;97&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Broken Staging Builds&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Frequent (API/Plugin issues)&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;95% Reduction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Content Schema Limits&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Restricted by Plan&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Unlimited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Vibe Check&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Frustrating&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;High-Five Worthy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;By moving our content into the codebase and using Claude to bridge the gap for non-technical users, we didn&amp;#39;t just speed up our site — we made our entire deployment pipeline more resilient.&lt;/p&gt;
&lt;p&gt;Because at the end of the day, the best way to fix a broken build is to remove the things that break it in the first place.&lt;/p&gt;
</content:encoded></item><item><title>You don’t need to pick one: how Sentry and OpenTelemetry work together</title><link>https://blog.sentry.io/sentry-opentelemetry-work-together/</link><guid isPermaLink="true">https://blog.sentry.io/sentry-opentelemetry-work-together/</guid><description>Use Sentry on the frontend, keep OpenTelemetry on the backend, and choose direct OTLP or Collector forwarding for OTLP events.</description><pubDate>Wed, 27 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You already instrumented the backend with OpenTelemetry. Your services emit spans. Your teams know the OTel APIs. Maybe you already run a Collector. So when you start evaluating Sentry, the obvious question is:&lt;/p&gt;
&lt;p&gt;Do you need to replace your OpenTelemetry setup with the Sentry SDK?&lt;/p&gt;
&lt;p&gt;No.&lt;/p&gt;
&lt;p&gt;The practical answer is usually: keep OpenTelemetry where it already works, add the Sentry SDK where it gives you more application context, and send OpenTelemetry Protocol (OTLP) events to Sentry. For a web app, that often means using the Sentry SDK on the frontend for browser tracing, errors, &lt;a href=&quot;https://sentry.io/product/logs/&quot;&gt;logs&lt;/a&gt;, &lt;a href=&quot;https://sentry.io/product/session-replay/&quot;&gt;Session Replay&lt;/a&gt;, and source maps, while keeping OpenTelemetry on the backend for existing service instrumentation.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;One scope note: OTLP can carry traces, logs, and metrics. At this moment, Sentry&amp;#39;s OTLP ingest supports logs and traces, not metrics. We&amp;#39;re considering adding support for them in the future.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The important part is separating two decisions that often get lumped together:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How traces stay connected across frontend and backend.&lt;/li&gt;
&lt;li&gt;How backend OTLP events are exported to Sentry.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you separate those, the architecture gets a lot easier to reason about.&lt;/p&gt;
&lt;h2&gt;Sentry vs OpenTelemetry is the wrong question&lt;/h2&gt;
&lt;p&gt;The first decision is trace linking. If a user clicks a button in your React app and that click triggers a backend request, the frontend and backend need to agree on the same &lt;a href=&quot;https://sentry.io/product/tracing/&quot;&gt;distributed trace&lt;/a&gt; context. In this example, the Sentry frontend SDK sends W3C &lt;code&gt;traceparent&lt;/code&gt; headers (configurable through the &lt;code&gt;propagateTraceparent&lt;/code&gt; option), and the OpenTelemetry backend continues the trace.&lt;/p&gt;
&lt;p&gt;That linking is handled by the frontend SDK configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;Sentry.init({
  integrations: [
    Sentry.browserTracingIntegration(),
  ],
  tracesSampleRate: 1.0,
  // ensure traceparent headers get sent
  propagateTraceparent: true,
  tracePropagationTargets: [
    &amp;#39;localhost&amp;#39;,
    &amp;#39;127.0.0.1&amp;#39;,
    /^http:\/\/localhost:8000\/api\//,
    /^http:\/\/127\.0\.0\.1:8000\/api\//,
    // your backend endpoint here
  ],
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second decision is export. After your backend creates telemetry, where do those OTLP events go?&lt;/p&gt;
&lt;p&gt;There are two common options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Send OTLP events directly from the backend to Sentry&amp;#39;s OTLP endpoint.&lt;/li&gt;
&lt;li&gt;Send OTLP events to an OpenTelemetry Collector, then have the Collector forward them to Sentry&amp;#39;s OTLP endpoint.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That trace-continuation step is what lets a Sentry-instrumented browser action become the parent of backend OpenTelemetry work, regardless of which OTLP export option you choose.&lt;/p&gt;
&lt;p&gt;If you want the reference docs for these pieces, start with &lt;a href=&quot;https://docs.sentry.io/concepts/otlp/sentry-with-otel/&quot;&gt;linking Sentry SDKs with OpenTelemetry SDKs&lt;/a&gt;, &lt;a href=&quot;https://docs.sentry.io/concepts/otlp/direct/traces/&quot;&gt;sending OpenTelemetry traces directly to Sentry&lt;/a&gt;, &lt;a href=&quot;https://docs.sentry.io/concepts/otlp/direct/logs/&quot;&gt;sending OpenTelemetry logs directly to Sentry&lt;/a&gt;, and &lt;a href=&quot;https://docs.sentry.io/concepts/otlp/forwarding/&quot;&gt;forwarding OpenTelemetry data to Sentry&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Direct OTLP vs Collector forwarding&lt;/h2&gt;
&lt;p&gt;Direct OTLP and Collector forwarding both end at Sentry&amp;#39;s OTLP endpoint. The difference is whether your service talks to Sentry itself or talks to a Collector first.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Use it when&lt;/th&gt;
&lt;th&gt;What you get&lt;/th&gt;
&lt;th&gt;Tradeoff&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Direct OTLP to Sentry&lt;/td&gt;
&lt;td&gt;You have one backend service or project and want the smallest setup&lt;/td&gt;
&lt;td&gt;Fewer moving parts and a short path from service to Sentry&lt;/td&gt;
&lt;td&gt;Less central control over processing, sampling, and routing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Collector forwarding&lt;/td&gt;
&lt;td&gt;You have multiple services, already run a Collector, need processing, or want multi-vendor routing&lt;/td&gt;
&lt;td&gt;Centralized routing, batching, processing, sampling, and easier vendor evaluation&lt;/td&gt;
&lt;td&gt;Another component to deploy and operate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Direct OTLP is the simplest path for a single backend project:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Collector forwarding is the better fit when your observability setup is already more than one app talking to one destination. It gives you one place to receive telemetry from many services, batch it, process it, and send it to one or more backends.&lt;/p&gt;
&lt;p&gt;That last part matters when you are evaluating Sentry. You can keep routing telemetry to an existing vendor while also forwarding a copy to Sentry, then compare the debugging experience without rewriting backend instrumentation.&lt;/p&gt;
&lt;p&gt;There is one important Sentry-specific detail for larger setups: a generic OTLP HTTP exporter points at one Sentry project endpoint with one project key. If you send every service through that one exporter, every service lands in the same Sentry project. For multi-project routing, use the &lt;a href=&quot;https://docs.sentry.io/concepts/otlp/forwarding/pipelines/sentry-exporter/&quot;&gt;Sentry exporter&lt;/a&gt;. It can route OTLP events to projects based on a resource attribute like &lt;code&gt;service.name&lt;/code&gt;, and it can auto-create missing projects when configured with the right Sentry API permissions.&lt;/p&gt;
&lt;h2&gt;A demo architecture&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s check out a &lt;a href=&quot;https://github.com/nikolovlazar/sentry-otel/tree/bf5e026c4923483e3d148d9fd30ead4d1540c774&quot;&gt;demo project&lt;/a&gt; that uses the Collector forwarding path:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;React + @sentry/react
  -&amp;gt; fetch with traceparent
  -&amp;gt; FastAPI + OpenTelemetry
  -&amp;gt; OpenTelemetry Collector
  -&amp;gt; Sentry OTLP endpoint
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The frontend lives in &lt;code&gt;frontend/&lt;/code&gt;. It is a React + Vite app using &lt;code&gt;@sentry/react&lt;/code&gt;. The backend lives in &lt;code&gt;backend/&lt;/code&gt;. It is a FastAPI service using the OpenTelemetry SDK, FastAPI instrumentation, SQLAlchemy instrumentation, manual spans, and standard logging in checkout logic. The Collector lives in &lt;code&gt;collector/&lt;/code&gt; and forwards those OTLP events to Sentry.&lt;/p&gt;
&lt;p&gt;The point of the demo is not that every layer uses the same SDK. The point is that every layer participates in the same trace, with backend logs attached to that debugging context.&lt;/p&gt;
&lt;h3&gt;The frontend uses the Sentry SDK&lt;/h3&gt;
&lt;p&gt;The Sentry setup is in &lt;a href=&quot;https://github.com/nikolovlazar/sentry-otel/blob/bf5e026c4923483e3d148d9fd30ead4d1540c774/frontend/src/instrument.ts&quot;&gt;&lt;code&gt;frontend/src/instrument.ts&lt;/code&gt;&lt;/a&gt;. It enables browser tracing, Session Replay, logs, and trace propagation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;Sentry.init({
  dsn: import.meta.env.VITE_SENTRY_DSN || undefined,
  environment: import.meta.env.MODE,
  sendDefaultPii: true,
  integrations: [
    Sentry.browserTracingIntegration(),
    Sentry.replayIntegration({
      maskAllText: false,
      blockAllMedia: false,
    }),
  ],
  enableLogs: true,
  tracesSampleRate: 1.0,
  propagateTraceparent: true,
  tracePropagationTargets: [
    &amp;#39;localhost&amp;#39;,
    &amp;#39;127.0.0.1&amp;#39;,
    /^http:\/\/localhost:8000\/api\//,
    /^http:\/\/127\.0\.0\.1:8000\/api\//,
  ],
  replaysSessionSampleRate: 1.0,
  replaysOnErrorSampleRate: 1.0,
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The frontend checkout flow is in &lt;a href=&quot;https://github.com/nikolovlazar/sentry-otel/blob/bf5e026c4923483e3d148d9fd30ead4d1540c774/frontend/src/hooks/useCheckoutLab.ts#L98-L107&quot;&gt;&lt;code&gt;frontend/src/hooks/useCheckoutLab.ts&lt;/code&gt;&lt;/a&gt;. It creates Sentry spans for user-facing work, logs useful state changes, and captures unexpected errors:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;await Sentry.startSpan(
  {
    name: &amp;#39;Run checkout scenario&amp;#39;,
    op: &amp;#39;ui.checkout&amp;#39;,
    attributes: {
      scenario,
      itemCount,
      totalCents,
    },
  },
  async () =&amp;gt; {
    const order = await createCheckout(cart, scenario)
    setLastOrder(order)
  },
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The actual request is a normal &lt;code&gt;fetch&lt;/code&gt; call:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const response = await fetch(`${API_BASE_URL}/api/checkout`, {
  method: &amp;#39;POST&amp;#39;,
  headers: { &amp;#39;content-type&amp;#39;: &amp;#39;application/json&amp;#39; },
  body: JSON.stringify({ items, scenario }),
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is no manual trace header code in that request. The Sentry browser tracing integration handles the propagation as long as the destination matches &lt;code&gt;tracePropagationTargets&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;The backend keeps OpenTelemetry&lt;/h3&gt;
&lt;p&gt;The backend does not install or initialize the Sentry SDK. Its OpenTelemetry setup is in &lt;a href=&quot;https://github.com/nikolovlazar/sentry-otel/blob/bf5e026c4923483e3d148d9fd30ead4d1540c774/backend/app/core/observability.py&quot;&gt;&lt;code&gt;backend/app/core/observability.py&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It creates an OpenTelemetry &lt;code&gt;TracerProvider&lt;/code&gt;, attaches service resource attributes, exports spans over OTLP HTTP, and registers W3C trace-context propagation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;resource = Resource.create(
    {
        **parse_resource_attributes(settings.otel_resource_attributes),
        &amp;quot;service.name&amp;quot;: settings.otel_service_name,
        &amp;quot;deployment.environment&amp;quot;: settings.app_environment,
    }
)
provider = TracerProvider(resource=resource)

if settings.otel_exporter_otlp_traces_endpoint:
    exporter = OTLPSpanExporter(endpoint=settings.otel_exporter_otlp_traces_endpoint)
    provider.add_span_processor(BatchSpanProcessor(exporter))

trace.set_tracer_provider(provider)
propagate.set_global_textmap(
    CompositePropagator(
        [
            TraceContextTextMapPropagator(),
            W3CBaggagePropagator(),
        ]
    )
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It also configures OTLP log export. The backend creates a &lt;code&gt;LoggerProvider&lt;/code&gt;, attaches an &lt;code&gt;OTLPLogExporter&lt;/code&gt;, and adds an OpenTelemetry &lt;code&gt;LoggingHandler&lt;/code&gt; to the app logger:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;provider = LoggerProvider(resource=build_resource(settings))
exporter = OTLPLogExporter(endpoint=settings.otel_exporter_otlp_logs_endpoint)
provider.add_log_record_processor(BatchLogRecordProcessor(exporter))
set_logger_provider(provider)

otel_handler = LoggingHandler(level=logging.INFO, logger_provider=provider)
app_logger = logging.getLogger(&amp;quot;checkout_trace_lab&amp;quot;)
app_logger.addHandler(otel_handler)
app_logger.setLevel(logging.INFO)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The default backend endpoints are local:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://localhost:4318/v1/logs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That means the backend exports OTLP traces and logs to the Collector, not directly to Sentry.&lt;/p&gt;
&lt;p&gt;The FastAPI app also allows the browser trace headers through CORS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;allow_headers=[
    &amp;quot;content-type&amp;quot;,
    &amp;quot;sentry-trace&amp;quot;,
    &amp;quot;baggage&amp;quot;,
    &amp;quot;traceparent&amp;quot;,
    &amp;quot;tracestate&amp;quot;,
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is easy to miss. If your browser is making cross-origin requests and CORS blocks the propagation headers, your frontend and backend traces can split apart even if both sides are instrumented correctly.&lt;/p&gt;
&lt;h3&gt;The checkout flow adds manual OTel spans and logs&lt;/h3&gt;
&lt;p&gt;The backend service code in &lt;a href=&quot;https://github.com/nikolovlazar/sentry-otel/blob/bf5e026c4923483e3d148d9fd30ead4d1540c774/backend/app/services/checkout.py&quot;&gt;&lt;code&gt;backend/app/services/checkout.py&lt;/code&gt;&lt;/a&gt; models a checkout workflow. It creates manual OpenTelemetry spans for business operations:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def validate_cart(engine: Engine, payload: CheckoutRequest) -&amp;gt; dict[str, ProductRow]:
    with tracer.start_as_current_span(&amp;quot;checkout.validate_cart&amp;quot;) as span:
        requested_ids = [item.product_id for item in payload.items]
        span.set_attribute(&amp;quot;cart.item_count&amp;quot;, len(payload.items))
        ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The checkout path includes spans for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;checkout.validate_cart&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;checkout.reserve_inventory&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;checkout.calculate_tax&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;checkout.payment&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;checkout.write_order&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;checkout.send_confirmation&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It also emits backend logs for the same business steps, such as &lt;code&gt;checkout.started&lt;/code&gt;, &lt;code&gt;checkout.cart_validated&lt;/code&gt;, &lt;code&gt;checkout.inventory_reserved&lt;/code&gt;, &lt;code&gt;checkout.payment_approved&lt;/code&gt;, &lt;code&gt;checkout.order_written&lt;/code&gt;, and &lt;code&gt;checkout.completed&lt;/code&gt;. Those logs go through Python&amp;#39;s standard &lt;code&gt;logging&lt;/code&gt; API, then the OpenTelemetry &lt;code&gt;LoggingHandler&lt;/code&gt; exports them through OTLP.&lt;/p&gt;
&lt;p&gt;This is the part OTel-first teams care about most. Those spans and logs stay in the OpenTelemetry pipeline. You do not need to rewrite them with &lt;code&gt;Sentry.startSpan()&lt;/code&gt; or Sentry logging APIs just to view the trace and related logs in Sentry.&lt;/p&gt;
&lt;h3&gt;The Collector forwards OTLP to Sentry&lt;/h3&gt;
&lt;p&gt;The Collector config is in &lt;a href=&quot;https://github.com/nikolovlazar/sentry-otel/blob/bf5e026c4923483e3d148d9fd30ead4d1540c774/collector/otel-collector.yaml&quot;&gt;&lt;code&gt;collector/otel-collector.yaml&lt;/code&gt;&lt;/a&gt;. It receives OTLP over gRPC and HTTP:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It batches the data:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;processors:
  batch:
    send_batch_size: 1024
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then it forwards to Sentry with the OTLP HTTP exporter:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;exporters:
  debug:
    verbosity: basic
  otlphttp/sentry:
    endpoint: ${env:SENTRY_OTLP_ENDPOINT}
    headers:
      x-sentry-auth: &amp;quot;sentry sentry_key=${env:SENTRY_OTLP_PUBLIC_KEY}&amp;quot;
    compression: gzip
    encoding: proto
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reminder: this demo uses generic &lt;code&gt;otlphttp&lt;/code&gt; for a single Sentry project. For multi-project routing or automatic project creation, swap it for the &lt;a href=&quot;https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/sentryexporter&quot;&gt;&lt;code&gt;sentry&lt;/code&gt; exporter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The configured pipelines send to both the debug exporter and Sentry:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug, otlphttp/sentry]
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug, otlphttp/sentry]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is the Collector forwarding pattern: the backend sends OTLP events to one local endpoint, and the Collector decides where that telemetry goes next.&lt;/p&gt;
&lt;h2&gt;What each layer is responsible for&lt;/h2&gt;
&lt;p&gt;The cleanest way to understand this architecture is by ownership.&lt;/p&gt;
&lt;p&gt;The frontend Sentry SDK owns browser-specific debugging context:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Browser spans and frontend transactions&lt;/li&gt;
&lt;li&gt;Frontend errors and React error boundaries&lt;/li&gt;
&lt;li&gt;Session Replay&lt;/li&gt;
&lt;li&gt;Frontend logs&lt;/li&gt;
&lt;li&gt;Source maps through the Sentry Vite plugin&lt;/li&gt;
&lt;li&gt;Trace propagation to the backend&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The backend OpenTelemetry SDK owns backend instrumentation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FastAPI request spans&lt;/li&gt;
&lt;li&gt;SQLAlchemy spans&lt;/li&gt;
&lt;li&gt;HTTPX spans if the backend calls other services&lt;/li&gt;
&lt;li&gt;Manual checkout spans&lt;/li&gt;
&lt;li&gt;Backend logs exported with OpenTelemetry&lt;/li&gt;
&lt;li&gt;Resource attributes such as &lt;code&gt;service.name&lt;/code&gt; and &lt;code&gt;deployment.environment&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;OTLP export to the Collector&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Collector owns routing and processing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Receiving OTLP on &lt;code&gt;4317&lt;/code&gt; and &lt;code&gt;4318&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Batching telemetry&lt;/li&gt;
&lt;li&gt;Printing debug output locally&lt;/li&gt;
&lt;li&gt;Forwarding to Sentry&amp;#39;s OTLP endpoint&lt;/li&gt;
&lt;li&gt;Providing the place to add sampling, transforms, filters, or multi-vendor routing later&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is why Sentry and OpenTelemetry are not competing choices here. They are doing different jobs in the same observability pipeline.&lt;/p&gt;
&lt;h2&gt;What you can see in Sentry&lt;/h2&gt;
&lt;p&gt;In the demo, I ran the checkout scenarios from the same frontend session. In Sentry, they show up as one connected trace that starts in React and continues through the FastAPI backend. You can see the normal checkout, slow payment, inventory miss, payment declined, and backend crash work in the same distributed trace instead of jumping between separate tools.&lt;/p&gt;
&lt;p&gt;The backend logs are associated with that trace too. Logs like &lt;code&gt;checkout.started&lt;/code&gt;, &lt;code&gt;checkout.inventory_reserved&lt;/code&gt;, &lt;code&gt;checkout.payment_slow_path&lt;/code&gt;, and &lt;code&gt;checkout.completed&lt;/code&gt; come from Python&amp;#39;s standard logging API, get exported through OTLP, and land in Sentry attached to the same debugging context as the spans.&lt;/p&gt;
&lt;p&gt;That is the useful part of the setup: the frontend SDK gives you browser context, the backend keeps its OpenTelemetry spans and logs, and Sentry gives you one place to inspect the full request path.&lt;/p&gt;
&lt;h2&gt;A decision tree for your own app&lt;/h2&gt;
&lt;p&gt;Use direct OTLP to Sentry when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have one backend service or one project.&lt;/li&gt;
&lt;li&gt;You want the fewest moving parts.&lt;/li&gt;
&lt;li&gt;You do not need central processing or multi-destination routing yet.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use Collector forwarding when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You already run an OpenTelemetry Collector.&lt;/li&gt;
&lt;li&gt;You have multiple backend services.&lt;/li&gt;
&lt;li&gt;You need services to land in separate Sentry projects.&lt;/li&gt;
&lt;li&gt;You need sampling, filtering, transforms, or batching outside the app process.&lt;/li&gt;
&lt;li&gt;You want to send telemetry to Sentry and another vendor while evaluating Sentry.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add the Sentry backend SDK later when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need error monitoring. OpenTelemetry does not capture and send errors to Sentry—only the Sentry SDK can capture backend exceptions and link them to the trace, so you can jump from an error to the full request path and inspect the related logs, spans, and context.&lt;/li&gt;
&lt;li&gt;You want profiling.&lt;/li&gt;
&lt;li&gt;You want &lt;a href=&quot;https://docs.sentry.io/product/metrics/&quot;&gt;Sentry&amp;#39;s Application Metrics&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;You want other Sentry features that are not represented by your current OpenTelemetry data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last step is optional, not a prerequisite. You can start with Sentry on the frontend and OpenTelemetry on the backend, then decide later whether adding the backend Sentry SDK is worth it for your services.&lt;/p&gt;
&lt;h2&gt;Start with the smallest change that preserves your trace&lt;/h2&gt;
&lt;p&gt;If your backend already uses OpenTelemetry, do not start by rewriting instrumentation. Start by deciding where OTLP events should go.&lt;/p&gt;
&lt;p&gt;For a single backend, direct OTLP to Sentry is usually enough.&lt;/p&gt;
&lt;p&gt;For multiple services, vendor evaluation, or anything that needs routing and processing, put a Collector in the middle.&lt;/p&gt;
&lt;p&gt;Then link your frontend Sentry SDK to your backend OTel SDK with W3C &lt;code&gt;traceparent&lt;/code&gt; propagation. That gives you the useful part first: one trace that starts where the user action starts and continues through the backend code you already instrumented.&lt;/p&gt;
&lt;p&gt;You do not need to pick Sentry or OpenTelemetry. Use both where they fit.&lt;/p&gt;
</content:encoded></item><item><title>Your agent can&apos;t fix what it can&apos;t see</title><link>https://blog.sentry.io/agents-need-production-context/</link><guid isPermaLink="true">https://blog.sentry.io/agents-need-production-context/</guid><description>Agents can&apos;t fix bugs they can&apos;t see. Learn how Sentry MCP and CLI give coding agents the production context to diagnose and fix issues automatically.</description><pubDate>Tue, 26 May 2026 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Agents are getting better and better at fixing bugs. They&amp;#39;re even getting better at testing their work, thanks to headless browsers, sandboxes, simulators, etc.&lt;/p&gt;
&lt;p&gt;But what about the bugs that only show up once you bring in different browsers, languages, extensions, internet speeds, and all the other variables that get mixed in the second you ship to prod? Or all the bugs that only show up when you account for… well, humans being humans and doing weird stuff you didn&amp;#39;t expect them to do?&lt;/p&gt;
&lt;p&gt;The bottleneck for self-healing software isn&amp;#39;t agent intelligence. It&amp;#39;s that agents have no idea what actually broke. They&amp;#39;re debugging from source code alone, which is roughly as effective as diagnosing a server outage by skimming the README. What they&amp;#39;re missing is production context: the stack trace, the request payload, the environment, the breadcrumbs leading up to the failure.&lt;/p&gt;
&lt;p&gt;Your agents need someone/something telling them what&amp;#39;s breaking in the wild &lt;em&gt;and&lt;/em&gt; giving them the context they need to understand why.&lt;/p&gt;
&lt;p&gt;We built &lt;a href=&quot;https://mcp.sentry.dev/&quot;&gt;Sentry MCP&lt;/a&gt; and the &lt;a href=&quot;https://cli.sentry.dev/&quot;&gt;Sentry CLI&lt;/a&gt; to make that context available to both humans, and increasingly as important, their agents. You can wire up a system today where a Sentry alert triggers an agent, the agent investigates the issue using the same evidence you would, and a draft PR with a fix lands in your repo before you open a browser.&lt;/p&gt;
&lt;h2&gt;Why draft PRs, not auto-merge&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s be honest about what&amp;#39;s realistic. A system that detects, fixes, tests, deploys, and monitors its own patches without human involvement is not something you should build today. That&amp;#39;s how you get a very exciting incident review.&lt;/p&gt;
&lt;p&gt;The useful version is more modest: a production error fires, an agent investigates it with real Sentry context, writes a small fix with a regression test, and opens a draft PR. A human is very much in the loop.&lt;/p&gt;
&lt;p&gt;That&amp;#39;s not fully autonomous, but it&amp;#39;s not trivial either. Most bugs sit in a queue, triaged, prioritized, assigned, waiting, and often lose out to new features. Seer diagnoses the root cause in under two minutes. A complete Autofix run, from root cause analysis to an opened PR, takes about six minutes.&lt;/p&gt;
&lt;p&gt;An agent that opens a reviewable, mergeable fix six minutes after the error fires is a meaningful change to your mean time to resolution, even if a human still clicks merge.&lt;/p&gt;
&lt;h2&gt;Two ways to give your agent production context&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Sentry MCP&lt;/strong&gt; is the right choice for agents that support the Model Context Protocol (Claude Code, Cursor, Codex, Windsurf, VS Code with Copilot). Your agent connects to the hosted server, authenticates via OAuth, and gets structured access to issues, events, traces, and Seer analysis. No local install required.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# One-liner for any MCP-compatible client
npx add-mcp https://mcp.sentry.dev/mcp

# Or for Claude Code specifically
claude mcp add --transport http sentry https://mcp.sentry.dev/mcp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your client doesn&amp;#39;t support the one-liner, add the config manually:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;mcpServers&amp;quot;: {
    &amp;quot;sentry&amp;quot;: {
      &amp;quot;url&amp;quot;: &amp;quot;https://mcp.sentry.dev/mcp&amp;quot;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The Sentry CLI&lt;/strong&gt; is the right choice for scripted workflows, CI pipelines, or any automation where you need structured output you can pipe to &lt;code&gt;jq&lt;/code&gt; or feed into another process.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl https://cli.sentry.dev/install -fsS | bash
sentry auth login
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&amp;#39;s what that looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ sentry issue list

Issues in acme/checkout:
╭──────────────┬──────────────────────────────────────────────────────┬──────┬─────┬────────┬───────┬──────────────╮
│ SHORT ID     │ ISSUE                                                │ SEEN │ AGE │ EVENTS │ USERS │ TRIAGE       │
├──────────────┼──────────────────────────────────────────────────────┼──────┼─────┼────────┼───────┼──────────────┤
│ CHECKOUT-P1  │ TimeoutError: Payment charge exceeded 30s            │   3h │  3h │  1.8k  │   340 │ High  86%    │
├──────────────┼──────────────────────────────────────────────────────┼──────┼─────┼────────┼───────┼──────────────┤
│ CHECKOUT-N7  │ TypeError: Cannot read property &amp;#39;total&amp;#39;              │   1d │  5d │    215 │    82 │ High  71%    │
├──────────────┼──────────────────────────────────────────────────────┼──────┼─────┼────────┼───────┼──────────────┤
│ API-34       │ RateLimitError: Too many requests to /v1/charges     │   3d │ 21d │     67 │    24 │ Med   42%    │
╰──────────────┴──────────────────────────────────────────────────────┴──────┴─────┴────────┴───────┴──────────────╯
Tip: Use &amp;#39;sentry issue view &amp;#39; to view details.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;CHECKOUT-P1&lt;/code&gt; is at the top, a timeout in the checkout service with 1.8k events and an 86% fixability score. Drill in:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ sentry issue view CHECKOUT-P1

CHECKOUT-P1: TimeoutError: Payment charge exceeded 30s
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
╭────────────┬─────────────────────────────────────────────╮
│ Status     │ ● Unresolved (Ongoing)                      │
│ Fixability │ High (86%)                                  │
│ Level      │ error                                       │
│ Platform   │ node                                        │
│ Project    │ checkout-service                            │
│ Events     │ 1832                                        │
│ Users      │ 340                                         │
│ First seen │ 3 hours ago                                 │
│ Last seen  │ 12 minutes ago                              │
│ Culprit    │ chargeCustomer (src/payment.ts)             │
│ Link       │ https://acme.sentry.io/issues/CHECKOUT-P1/  │
╰────────────┴─────────────────────────────────────────────╯

Tip: Use &amp;#39;sentry issue explain CHECKOUT-P1&amp;#39; for AI root cause analysis
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks like a straightforward timeout. An agent with just this would add retry logic or bump the timeout. But run &lt;code&gt;sentry issue explain&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ sentry issue explain CHECKOUT-P1

ℹ Starting root cause analysis, it can take several minutes...

Root Cause Analysis Complete
━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Cause #0: The checkout service&amp;#39;s /charge endpoint times out
waiting for the payment service, which blocks on an inventory
availability check. The inventory service&amp;#39;s check_stock query
regressed from ~200ms to ~28s after migration
0047_drop_unused_indexes removed the compound index on
(product_id, warehouse_id).

Repository: acme/inventory-service
Affected: src/queries/check_stock.ts:18
First seen: release-3.1.0 (deployed 3h ago)

Reproduction steps:
1. User submits checkout → POST /charge
2. Payment service calls inventory.check_stock(items)
3. check_stock runs full table scan (missing index) → 28s
4. Payment call exceeds 30s timeout → TimeoutError bubbles up to checkout

To create a plan, run: sentry issue plan CHECKOUT-P1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The root cause isn&amp;#39;t in the checkout service at all. It&amp;#39;s a dropped database index in the inventory service, two hops away in the trace. No amount of retry logic in &lt;code&gt;payment.ts&lt;/code&gt; fixes that.&lt;/p&gt;
&lt;h2&gt;From alert to draft PR&lt;/h2&gt;
&lt;p&gt;When a Sentry alert fires on a new or regressed issue, a webhook triggers a worker that checks out your repo and runs a coding agent with a prompt grounded in the specific issue:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A production error was captured by Sentry. The issue ID is CHECKOUT-P1.

Use Sentry MCP to retrieve the full issue details: stack trace,
breadcrumbs, tags, release, environment, distributed traces,
suspect commits, and Seer analysis.

Based on the evidence:

1. Identify the root cause. Follow traces across services.
2. Make the smallest safe fix in the right repository.
3. Add or update a regression test that covers this failure.
4. Run the test suite.
5. Open a draft PR with the Sentry issue link, root-cause
   summary, files changed, and test results.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The agent pulls the issue via MCP. The distributed trace shows the checkout call chaining through the payment service into an inventory check that&amp;#39;s taking 28 seconds. Metrics confirm the inventory service&amp;#39;s p99 spiked from 200ms to 28s three hours ago. Suspect commits point at a migration in &lt;code&gt;acme/inventory-service&lt;/code&gt; that dropped a compound index. Session replay shows users rage-clicking &amp;quot;Pay&amp;quot; while nothing happens, generating duplicate charge attempts.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sentry issue plan CHECKOUT-P1&lt;/code&gt; lays out the fix: restore the compound index on &lt;code&gt;(product_id, warehouse_id)&lt;/code&gt;. A draft PR lands in &lt;code&gt;acme/inventory-service&lt;/code&gt; with the migration, a root-cause summary linking back to the Sentry trace, and a regression test.&lt;/p&gt;
&lt;h2&gt;Try it with Cursor Automations&lt;/h2&gt;
&lt;p&gt;We publish a &lt;a href=&quot;https://sentry.io/cookbook/regressed-issue-to-pr-cursor/&quot;&gt;cookbook recipe&lt;/a&gt; for this exact workflow using Cursor&amp;#39;s Automations feature. It walks through connecting your repo to Sentry, adding the MCP server to an automation, and configuring a webhook alert to trigger on regressed issues.&lt;/p&gt;
&lt;p&gt;Because Sentry knows the release history and suspect commits, the agent doesn&amp;#39;t search the entire repo for the problem. It starts where the evidence points. For regressed issues specifically, it can identify which commit reintroduced the bug, read the original fix, and understand what went wrong the second time around.&lt;/p&gt;
&lt;h2&gt;What&amp;#39;s next&lt;/h2&gt;
&lt;p&gt;The more telemetry your app sends to Sentry (traces, metrics, logs, session replays), the harder the bugs an agent can tackle. Today it&amp;#39;s dropped indexes across service boundaries. Six months ago it was null checks. The merge rate on Autofix PRs has climbed from 41% to 46% in that time, and the diagnosis complexity is growing with it.&lt;/p&gt;
&lt;p&gt;There are real limits. Bugs that need product judgment, issues in code the agent can&amp;#39;t reach, and problems where there isn&amp;#39;t enough telemetry to connect the dots: those still need you. But the surface area of what agents can fix is expanding every month.&lt;/p&gt;
&lt;p&gt;Connect &lt;a href=&quot;https://mcp.sentry.dev/&quot;&gt;Sentry MCP&lt;/a&gt; to your editor or install the &lt;a href=&quot;https://cli.sentry.dev&quot;&gt;CLI&lt;/a&gt;. Hook up your repos for code mappings and tracing. Run &lt;code&gt;sentry issue explain&lt;/code&gt; on something that&amp;#39;s been sitting in your backlog and see what it finds.&lt;/p&gt;
&lt;p&gt;Check out the &lt;a href=&quot;https://docs.sentry.io/product/ai-in-sentry/seer/autofix/&quot;&gt;Seer Autofix docs&lt;/a&gt; for more on coding agent handoff to Claude Code and Cursor.&lt;/p&gt;
</content:encoded></item><item><title>The product analytics you already have</title><link>https://blog.sentry.io/product-analytics-you-already-have/</link><guid isPermaLink="true">https://blog.sentry.io/product-analytics-you-already-have/</guid><description>Your Sentry traces, logs, and metrics already answer most product analytics questions. Learn how to query existing telemetry for product insights.</description><pubDate>Thu, 21 May 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You already have everything you need.&lt;/p&gt;
&lt;p&gt;If you&amp;#39;re using Sentry, you have &lt;a href=&quot;https://sentry.io/product/tracing/&quot;&gt;traces&lt;/a&gt;, &lt;a href=&quot;https://sentry.io/product/logs/&quot;&gt;structured logs&lt;/a&gt;, and now &lt;a href=&quot;https://sentry.io/product/metrics/&quot;&gt;application metrics&lt;/a&gt;. Most teams use that stuff for debugging and stop there. But get this: that same data can answer most of the product questions you&amp;#39;ve been sending to a separate analytics tool, maintained by a separate team, with a separate data model and a separate bill. (Not all of them. We&amp;#39;ll get honest about the gaps later.)&lt;/p&gt;
&lt;p&gt;This isn&amp;#39;t a post about whether product analytics tools should exist. It&amp;#39;s about the fact that developers have been sitting on top of a goldmine of product insight and outsourcing the questions to someone else. You don&amp;#39;t have to.&lt;/p&gt;
&lt;p&gt;There&amp;#39;s a reason this matters more now than it did five years ago. The line between &amp;quot;product manager&amp;quot; and &amp;quot;software engineer&amp;quot; is blurring. Engineers are increasingly expected to think about adoption, retention, and user behavior, not just uptime and latency. If you&amp;#39;re a product engineer (and increasingly, that&amp;#39;s just &amp;quot;engineer&amp;quot;), the tools you already use for debugging are the same tools you should be using to answer product questions. You just haven&amp;#39;t been querying them that way.&lt;/p&gt;
&lt;h2&gt;Every product question maps to telemetry you already have&lt;/h2&gt;
&lt;p&gt;&amp;quot;How many users completed onboarding this week?&amp;quot; That&amp;#39;s a counter metric: &lt;code&gt;metrics.count(&amp;quot;onboarding.completed&amp;quot;, 1)&lt;/code&gt;. You can slice it by plan tier, country, or referral source with attributes you&amp;#39;re already setting.&lt;/p&gt;
&lt;p&gt;&amp;quot;What&amp;#39;s our p95 checkout latency by region?&amp;quot; That&amp;#39;s a distribution on a span. The trace already has the timing. You just need to query it.&lt;/p&gt;
&lt;p&gt;&amp;quot;Why did signups drop on Tuesday?&amp;quot; That&amp;#39;s a structured log. The signup service logged &lt;code&gt;signup.failed&lt;/code&gt; with &lt;code&gt;reason: email_validation_error&lt;/code&gt; and &lt;code&gt;deploy_sha: a3f9b2c&lt;/code&gt;. The log is already correlated to the trace that shows the full request lifecycle, the span that errored, and the release that introduced it. From the trace, you&amp;#39;re one click away from the issue, which links you to the commit and the line of code that caused it.&lt;/p&gt;
&lt;p&gt;These are the same questions your PM asks their analytics tool. The difference is that when &lt;em&gt;you&lt;/em&gt; answer them from your telemetry, the answer comes with context. The analytics tool gives you the &amp;quot;what.&amp;quot; Your telemetry gives you the &amp;quot;what,&amp;quot; the &amp;quot;why,&amp;quot; and a direct path to the code that&amp;#39;s responsible.&lt;/p&gt;
&lt;p&gt;A customer told us recently that they were tired of analytics tools giving them a heads-up &lt;em&gt;after&lt;/em&gt; the damage was done. They wanted to be proactive. The way they got there was setting up alerts and monitors on the telemetry they were already collecting. When a business-critical metric starts trending down, the alert fires before the weekly dashboard review catches it. And because the alert is on a span or a metric that&amp;#39;s connected to the full trace, the investigation starts with context, not a context-switch to a different tool. They didn&amp;#39;t change the data they were collecting. They changed how they were watching it.&lt;/p&gt;
&lt;h2&gt;What it looks like in practice&lt;/h2&gt;
&lt;p&gt;Say you want to know whether users are adopting your new export feature, whether it&amp;#39;s performant, and whether paid users behave differently than free users.&lt;/p&gt;
&lt;p&gt;You don&amp;#39;t need to file a ticket asking someone to instrument this in an analytics tool. You already have the three primitives: spans, logs, and application metrics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Spans for request-level context.&lt;/strong&gt; Spans tell you how requests move through your system and how long each operation takes. The export API handler already has a span. Add business-level attributes (like &lt;code&gt;export.user_tier&lt;/code&gt;, which lets you slice by plan):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;with sentry_sdk.start_span(op=&amp;quot;export.generate&amp;quot;, name=&amp;quot;Generate export file&amp;quot;) as span:
    span.set_attribute(&amp;quot;export.file_size_mb&amp;quot;, file_size_mb)
    span.set_attribute(&amp;quot;export.format&amp;quot;, export_format)
    span.set_attribute(&amp;quot;export.user_tier&amp;quot;, user.plan)
    span.set_attribute(&amp;quot;export.row_count&amp;quot;, row_count)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Query adoption, performance, and errors in one place:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;span.op:export.generate | count(), avg(span.duration), count_if(span.status:internal_error)
  group by export.user_tier, export.format
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;export.user_tier  | export.format | count() | avg(span.duration) | error_count
──────────────────|───────────────|─────────|────────────────────|────────────
pro               | xlsx          | 1,204   | 3.8s               | 89
pro               | csv           | 3,847   | 1.2s               | 24
free              | csv           | 12,493  | 0.9s               | 3
free              | xlsx          | 891     | 4.1s               | 142
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can already see the story: xlsx exports are 3-4x slower than csv, and free-tier xlsx is erroring on 16% of requests. You didn&amp;#39;t need an analytics tool to surface that. You needed to query the spans you already had.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Structured logs for discrete business events.&lt;/strong&gt; Logs capture discrete events with enough detail to help you debug what happened and why. When a user upgrades their plan, you want to record the events that took place before and after the plan change, where it was triggered, and any revenue impact. That&amp;#39;s a business event worth recording, but it&amp;#39;s not a performance-sensitive operation you need to trace end-to-end. Log it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;

sentry_sdk.logger.info(
    &amp;quot;plan.upgraded&amp;quot;,
    previous_plan=previous_plan,
    new_plan=new_plan,
    user_id=user.id,
    upgrade_source=source,  # &amp;quot;paywall&amp;quot;, &amp;quot;settings&amp;quot;, &amp;quot;checkout&amp;quot;
    mrr_delta_usd=mrr_change,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can query plan upgrades by source, see which upgrade path drives the most revenue, and if something breaks in the upgrade flow, the log entry is already linked to the trace context that shows you where it failed.&lt;/p&gt;
&lt;p&gt;Query your logs for upgrade events from the past 30 days:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;plan.upgraded | count(), sum(mrr_delta_usd)
  group by upgrade_source
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;upgrade_source | count() | sum(mrr_delta_usd)
───────────────|─────────|───────────────────
paywall        | 842     | $27,340
settings       | 214     | $8,120
checkout       | 1,307   | $51,890
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Checkout drives the most upgrades, but it also converts at a higher average MRR per upgrade ($39.69 vs. $32.47 for paywall). That&amp;#39;s the kind of insight your PM is running a separate tool to get, and it&amp;#39;s sitting in your logs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Application Metrics for KPIs and health signals.&lt;/strong&gt; Metrics are how you track the measures that tell you whether things are healthy – both in your service and your business. Things like checkout conversion rate, signup and traffic over time, and error budget burn. These are the signals you track continuously, get alerted on, and study trends:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;from sentry_sdk import metrics

metrics.count(&amp;quot;export.completed&amp;quot;, 1, attributes={&amp;quot;tier&amp;quot;: user.plan, &amp;quot;format&amp;quot;: export_format})
metrics.distribution(&amp;quot;export.file_size&amp;quot;, file_size_mb, attributes={&amp;quot;tier&amp;quot;: user.plan})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives you a counter and distribution you can alert on, visualize, and use to observe trends over time without building or maintaining an analytics pipeline.&lt;/p&gt;
&lt;p&gt;That&amp;#39;s three primitives in one SDK covering every angle you need: request-level detail from spans, discrete business events from logs, and aggregated trends from metrics, all queryable in one tool and all connected to each other.&lt;/p&gt;
&lt;p&gt;Compare this to the usual workflow: someone instruments a &lt;code&gt;feature_export_used&lt;/code&gt; event in an analytics tool, builds a dashboard, checks it weekly. Three weeks later they notice adoption is flat. They ask engineering if there are issues. Engineering checks Sentry, finds the export is timing out for files over 50MB, which covers most real-world usage. Three weeks lost because the analytics tool could see the symptom but not the cause.&lt;/p&gt;
&lt;p&gt;With your telemetry, you can set up a monitor on that span&amp;#39;s error rate and duration. When the timeout starts happening, the alert fires on the span itself, not on a downstream analytics metric that takes weeks to reflect the problem. The metric shows the count dropping. The span shows the duration spiking. The log shows the error. And all three point back to the same trace, the same release, the same line of code.&lt;/p&gt;
&lt;p&gt;To go one step further, you can ask &lt;a href=&quot;https://sentry.io/product/seer/&quot;&gt;Seer&lt;/a&gt; if anything is broken that may be causing adoption to stay flat.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/product-analytics-you-already-have/ask-seer-analytics.webp&quot; alt=&quot;Asking Seer to analyze product analytics data&quot;&gt;&lt;/p&gt;
&lt;h2&gt;The skills already transfer&lt;/h2&gt;
&lt;p&gt;If you&amp;#39;re already using Sentry for debugging, the only mental shift is realizing that &amp;quot;business telemetry&amp;quot; and &amp;quot;system telemetry&amp;quot; aren&amp;#39;t different categories.&lt;/p&gt;
&lt;p&gt;The business question (&amp;quot;did the user convert?&amp;quot;) and the engineering question (&amp;quot;did the request succeed?&amp;quot;) are the same question asked at different altitudes. You don&amp;#39;t need a separate tool to ask the business question. You need to add &lt;code&gt;purchase.value_usd&lt;/code&gt; to the span you already have, log the &lt;code&gt;purchase.completed&lt;/code&gt; event with the attributes that matter, and increment the counter.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;# On the span
span.set_data(&amp;quot;purchase.value_usd&amp;quot;, order.total)
span.set_data(&amp;quot;purchase.item_count&amp;quot;, len(order.items))

# As a structured log
sentry_sdk.logger.info(
    &amp;quot;purchase.completed&amp;quot;,
    value_usd=order.total,
    item_count=len(order.items),
    coupon_applied=bool(order.coupon),
    user_id=user.id,
)

# As a metric
metrics.count(&amp;quot;purchase.completed&amp;quot;, 1, attributes={&amp;quot;coupon&amp;quot;: str(bool(order.coupon))})
metrics.distribution(&amp;quot;purchase.value&amp;quot;, order.total, attributes={&amp;quot;plan&amp;quot;: user.plan})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One customer framed it well: when there&amp;#39;s a discrepancy in a business metric, it shows up in other parts of the system too. That&amp;#39;s what &lt;a href=&quot;https://sentry.io/solutions/application-observability/&quot;&gt;observability&lt;/a&gt; means. Everything is connected. The moment you start treating your telemetry as the source of truth for product questions, you stop needing a second system to answer them.&lt;/p&gt;
&lt;h2&gt;Where this gets hard (and why it&amp;#39;s getting easier)&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s be honest about the gaps.&lt;/p&gt;
&lt;p&gt;Multi-session retention analysis, behavioral cohorts (&amp;quot;users who did X but not Y within 14 days&amp;quot;), and cross-session funnel conversion are genuinely difficult to reconstruct from raw telemetry. Spans are request-scoped. Logs are event-scoped. Metrics are time-series. Stitching together a user&amp;#39;s journey across sessions and days requires aggregation infrastructure that observability tools haven&amp;#39;t traditionally built.&lt;/p&gt;
&lt;p&gt;If your PM needs a 30-day retention curve segmented by acquisition channel, you can&amp;#39;t just &lt;code&gt;GROUP BY&lt;/code&gt; your way there today.&lt;/p&gt;
&lt;p&gt;But most of the data is already there. Your spans, logs, and metrics all carry user IDs, timestamps, and business attributes. The missing piece is the aggregation and visualization layer. That&amp;#39;s a query engine problem, not a data model problem. OpenTelemetry is making this easier to solve every quarter, because once instrumentation is standardized, the aggregation layer becomes commoditized. The gap is real, and it&amp;#39;s shrinking.&lt;/p&gt;
&lt;p&gt;We think there&amp;#39;s interesting work to be done here, and we plan to dig into some of these harder use cases in future posts, showing how far you can get with Sentry&amp;#39;s existing query tools even for cross-session analysis.&lt;/p&gt;
&lt;p&gt;For the questions that matter day-to-day as a developer building and shipping features, the gap doesn&amp;#39;t exist. Is my feature being adopted? Is it performant? Is it erroring? Are paid users behaving differently than free users? Which upgrade path drives the most revenue? You can answer all of these right now, from the telemetry you already have, without waiting for anyone.&lt;/p&gt;
&lt;h2&gt;Try it&lt;/h2&gt;
&lt;p&gt;Pick one feature you shipped recently that you&amp;#39;re curious about. There most certainly is an opportunity to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add business-level attributes to the spans on the critical path of that feature&amp;#39;s functionality&lt;/li&gt;
&lt;li&gt;Add Sentry Logs that are high cardinality wide events including details you would want to query this data by (user plan, surface, activity data)&lt;/li&gt;
&lt;li&gt;Sprinkle application metrics across with attributes that will be useful in creating dashboards.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We have found that agents are quite good at following these types of requests, and we have some skills to deploy in your local IDE and once you&amp;#39;re done, &lt;a href=&quot;https://sentry.io/product/seer/agent/&quot;&gt;ask Seer Agent&lt;/a&gt; about the best ways to create dashboards, monitors and alerts for them.&lt;/p&gt;
&lt;p&gt;See how long it takes before the analytics dashboard for that feature stops being the thing anyone opens first.&lt;/p&gt;
&lt;p&gt;You already have the data, you already have the tools, and you&amp;#39;ve just been letting someone else ask your questions for you.&lt;/p&gt;
</content:encoded></item><item><title>New ways to agentically build and edit dashboards</title><link>https://blog.sentry.io/dashboard-updates/</link><guid isPermaLink="true">https://blog.sentry.io/dashboard-updates/</guid><description>Create and edit Sentry dashboards with AI agents, the Sentry CLI, or pre-built templates you can clone and customize for your monitoring needs.</description><pubDate>Thu, 14 May 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The traditional dashboard workflow, teams slowly handcrafting visualizations to track critical KPIs, is dying in a world of AI agents.&lt;/p&gt;
&lt;p&gt;A few years ago, in a pre-agentic-everything world, we tried to make it easier for developers to monitor critical experiences. We introduced Insights pages, which were pre-configured dashboards any Sentry user could adopt instantly that surfaced common health signals, like Web and Mobile Vitals.&lt;/p&gt;
&lt;p&gt;The idea was right, but there was a problem: while many companies share common signals, every organization is unique. Without meaningful customization, most teams still ended up having to slog through manually building dashboards themselves. So we kept iterating.&lt;/p&gt;
&lt;p&gt;Large language models are what finally made a reality of on-demand, customizable dashboards possible. Visualizations remain one of the most information-dense ways for humans (and agents) to communicate. What changed is the cost of creating those views.&lt;/p&gt;
&lt;p&gt;Instead of assembling dashboards widget by widget, you can now prompt an agent to create or edit dashboards directly in Sentry, or use the &lt;a href=&quot;https://cli.sentry.dev/&quot;&gt;Sentry CLI&lt;/a&gt; to connect Sentry to other models, and generate a dashboard tailored to the task at hand.&lt;/p&gt;
&lt;p&gt;Insights pages are now &lt;a href=&quot;https://docs.sentry.io/product/dashboards/sentry-dashboards/&quot;&gt;Sentry-built Dashboards&lt;/a&gt;. Clone them, then ask an agent to customize them for your project. Dashboards can now be created in seconds, used for the lifetime of a project or investigation, and discarded once they stop providing value.&lt;/p&gt;
&lt;h2&gt;What&amp;#39;s new&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Agentic dashboard creation &amp;amp; editing (beta):&lt;/strong&gt; All organizations with &lt;a href=&quot;https://docs.sentry.io/product/ai-in-sentry/&quot;&gt;AI-powered features enabled&lt;/a&gt; can now create and edit dashboards in Sentry using an agent-powered chat experience.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Insights are now Sentry dashboards:&lt;/strong&gt; We replaced Insights pages with clonable, editable Sentry dashboards that you can customize to fit your specific use case.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dashboard creation &amp;amp; editing via the Sentry CLI:&lt;/strong&gt; You can create and manage Sentry dashboards from your terminal via the all-new Sentry CLI.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Sentry dashboards use issue, &lt;a href=&quot;https://docs.sentry.io/concepts/key-terms/tracing/&quot;&gt;tracing&lt;/a&gt; and &lt;a href=&quot;https://docs.sentry.io/product/metrics/getting-started/&quot;&gt;application metrics&lt;/a&gt; data. You can also &lt;a href=&quot;https://docs.sentry.io/product/trace-explorer/&quot;&gt;query against multiple event types&lt;/a&gt; in Sentry and save queries to dashboards.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Agentic dashboard generation and editing&lt;/h2&gt;
&lt;p&gt;You can now create and edit dashboards in Sentry agentically via the same capabilities that power &lt;a href=&quot;https://sentry.io/product/seer/&quot;&gt;Seer, Sentry&amp;#39;s AI debugger&lt;/a&gt; (&lt;strong&gt;Note&lt;/strong&gt;: while Seer is an add-on option, agentic dashboard creation is a separate feature that is available for free). When you create or edit a dashboard, you still have the option to add or edit manually, but you can now just tell Sentry what you want and we&amp;#39;ll put it together for you automatically. Creating and editing dashboards with AI makes the experience of going from concept to final dashboard much faster than manual creation:&lt;/p&gt;
&lt;p&gt;It&amp;#39;s a best practice to use agentic dashboard creation as a starting point for a new dash or edits to an existing dash, and then verify and tweak the updates to make sure it&amp;#39;s exactly what you want. This experience is still in open beta, so we&amp;#39;re still ironing out some of the kinks.&lt;/p&gt;
&lt;p&gt;Many more dashboards can now easily be created. With this in mind, we&amp;#39;ve created a markdown widget to encourage you to leave notes and document what you&amp;#39;ve built:&lt;/p&gt;
&lt;h3&gt;Dashboard revision history&lt;/h3&gt;
&lt;p&gt;Dashboards now maintain a revision history. Any edits made through the UI, Seer agent, or API are automatically tracked. To view prior revisions, click the clock icon in the upper-right corner.&lt;/p&gt;
&lt;p&gt;If you, or a 🤖, make a mistake, you can restore a known-good version by selecting a previous revision and clicking &lt;strong&gt;Revert to Selection&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;Sentry use case: fixing and monitoring jest tests&lt;/h3&gt;
&lt;p&gt;Last week, we increased the number of CI runners we use for jest tests, moving from 4 to 8 on our main branch. We&amp;#39;ve had 4 runners for years to parallelize the work. Total CI time depends on how long the slowest runner takes to do its work; to make sure that everything finishes at the same time we measure the duration to run each test file and parallelize based on that. More runners should mean faster CI time.&lt;/p&gt;
&lt;p&gt;Instead, we noticed that overall CI time had been regressing for a few days, going from 6 minutes up to 10. The culprit was that the balancer script wasn&amp;#39;t running successfully because tests were failing out. Once we fixed some flaky tests, that time went down from ~10 minutes to less than 4. Our new runner configuration did improve overall CI time after all!&lt;/p&gt;
&lt;p&gt;To prevent this from happening again we used agentic dashboard creation (plus some manual metric creation) to whip up some new dashboards (and monitors) to keep tabs on flakes, balance failures, and overall slowness:&lt;/p&gt;
&lt;h2&gt;Insights are now Sentry dashboards&lt;/h2&gt;
&lt;p&gt;In the Dashboard nav item, you&amp;#39;ll see the option to explore Sentry Built dashboards:&lt;/p&gt;
&lt;p&gt;This is a &lt;a href=&quot;https://docs.sentry.io/product/dashboards/sentry-dashboards/&quot;&gt;collection of dashboards pre-built by Sentry&lt;/a&gt; to address common monitoring use cases. The Sentry-built dashboards cannot be edited, but they can be duplicated to create custom dashboards. You can think of them as templates that can be adapted to address your specific use case.&lt;/p&gt;
&lt;p&gt;On the &lt;strong&gt;frontend&lt;/strong&gt; side, the &lt;a href=&quot;https://docs.sentry.io/product/dashboards/sentry-dashboards/frontend/web-vitals/&quot;&gt;Web Vitals&lt;/a&gt; dashboard surfaces LCP, INP, CLS, and TTFB across your real users, broken down by page, with a Performance Score that flags which pages have the most room to improve. Frontend Session Health connects deployments to crash and error rates, so you can see when a release tanked your stability. Frontend Assets shows you which JS and CSS assets are slow or render-blocking, useful when LCP regressed and you don&amp;#39;t yet know why.&lt;/p&gt;
&lt;p&gt;On the &lt;strong&gt;backend&lt;/strong&gt; side, you get dashboards for slow queries (with drill-downs into individual query summaries and sample events), cache hit/miss rates, queue throughput and processing latency, and outbound API requests grouped by domain. The &lt;a href=&quot;https://docs.sentry.io/product/dashboards/sentry-dashboards/backend/&quot;&gt;Backend Overview&lt;/a&gt; ties these together with p50/p75 duration and the most time-consuming queries and domains.&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;mobile&lt;/strong&gt;, there&amp;#39;s &lt;a href=&quot;https://docs.sentry.io/product/dashboards/sentry-dashboards/mobile/mobile-vitals/&quot;&gt;Mobile Vitals&lt;/a&gt; (cold/warm app starts, slow and frozen frame rates, TTID/TTFD), &lt;a href=&quot;https://docs.sentry.io/product/dashboards/sentry-dashboards/mobile/session-health/&quot;&gt;Mobile Session Health&lt;/a&gt; (crash-free sessions and users, with release annotations), and dedicated drill-downs for app starts and screen rendering.&lt;/p&gt;
&lt;p&gt;There are also &lt;strong&gt;framework-specific dashboards&lt;/strong&gt;: a &lt;a href=&quot;https://docs.sentry.io/product/dashboards/sentry-dashboards/nextjs/&quot;&gt;Next.js Overview&lt;/a&gt; with a tree-based SSR view for finding performance bottlenecks, and a Laravel Overview tuned for Laravel-specific metrics.&lt;/p&gt;
&lt;h2&gt;Creating dashboards via the Sentry CLI&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://cli.sentry.dev/&quot;&gt;Sentry CLI&lt;/a&gt; includes a full &lt;code&gt;dashboard&lt;/code&gt; command, bringing dashboard creation and management out of the browser and into your shell. You can list, view, create, and modify dashboards, including adding, editing, and deleting individual widgets, without ever leaving your terminal. With the new &lt;code&gt;dashboard&lt;/code&gt; command, dashboards become code-adjacent artifacts: scriptable, repeatable, and reviewable. Define dashboards in shell scripts or CI pipelines, commit them alongside your application code, and roll them out the same way you ship features.&lt;/p&gt;
&lt;p&gt;Every command supports &lt;code&gt;--json&lt;/code&gt; output, making it straightforward for scripts, internal tools, or AI coding agents to provision and update dashboards programmatically (that means you can easily create dashboards from Claude Code, GitHub Copilot, Cursor, or your agent of choice).&lt;/p&gt;
&lt;h3&gt;Sentry use case: investigating integrations&lt;/h3&gt;
&lt;p&gt;In order to test out a potential &lt;a href=&quot;https://github.com/getsentry/sentry-ruby/pull/2925&quot;&gt;yabeda integration&lt;/a&gt; for &lt;a href=&quot;https://docs.sentry.io/product/metrics/&quot;&gt;application metrics&lt;/a&gt;, one of our engineers created a custom dashboard with the CLI and &lt;a href=&quot;https://github.com/dingsdax/fizzy/blob/e6c2c4dc3f30fa016c72c28d7371608065cae2cd/docs/sentry-dashboard.md&quot;&gt;documented the process&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Get started&lt;/h3&gt;
&lt;p&gt;Whether you&amp;#39;re cloning a Sentry-built dashboard as a starting point, prompting an agent to spin one up from scratch, or scripting dashboard creation directly from your terminal, the goal is the same: spend less time building the view and more time acting on what it shows you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sentry Dashboards&lt;/strong&gt; are available now on all plans. Open &lt;a href=&quot;https://sentry.io/orgredirect/organizations/:orgslug/dashboards/&quot;&gt;Dashboards&lt;/a&gt; to see them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom Dashboards&lt;/strong&gt; are available on all plans. Click &lt;strong&gt;Create Dashboard&lt;/strong&gt; to start from scratch, or duplicate a pre-built dashboard.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agentic Dashboard creation&lt;/strong&gt; is live now for organizations with AI features enabled. If you don&amp;#39;t see the &lt;strong&gt;Generate Dashboard&lt;/strong&gt; option, check your org&amp;#39;s AI settings.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have questions or feedback?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Join the conversation &lt;a href=&quot;https://discord.com/invite/sentry&quot;&gt;in our Discord&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;New to Sentry?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Try &lt;a href=&quot;https://sentry.io/signup/&quot;&gt;Sentry for free&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>From vibe code to production-ready: observability for Next.js and Supabase apps</title><link>https://blog.sentry.io/nextjs-supabase-observability/</link><guid isPermaLink="true">https://blog.sentry.io/nextjs-supabase-observability/</guid><description>Instrument Next.js and Supabase with Sentry to get unified errors, distributed traces, logs, performance insights, and AI-assisted fixes.</description><pubDate>Mon, 11 May 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The way we build software has drastically changed over the past few years. What hasn&amp;#39;t changed is that this software ends up in front of real people: you, me, my mom.&lt;/p&gt;
&lt;p&gt;And when those users inevitably run into something broken, you as the application&amp;#39;s developer need to be equipped with the right tools, context and understanding of what broke, where it broke, and how to fix it as quickly as possible.&lt;/p&gt;
&lt;p&gt;Every day we&amp;#39;re inching closer to self-healing software. If you are building a Next.js application and are using Supabase as the backend service, the tooling described below can help you get one step closer to a self-closing loop of producing quality software and fixing what slipped through the cracks with minimal disruption.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Supabase gives you query performance insights, row-level security (RLS) advisories, and edge function logs out of the box, but it can&amp;#39;t trace across your full stack&lt;/li&gt;
&lt;li&gt;Sentry fills that gap: &lt;a href=&quot;https://docs.sentry.io/platforms/javascript/guides/nextjs/tracing/distributed-tracing/&quot;&gt;distributed traces&lt;/a&gt; from your Next.js frontend through Supabase Edge Functions to Postgres, all in one place&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.sentry.io/product/drains/supabase/&quot;&gt;Log draining from Supabase&lt;/a&gt; into Sentry gives you a single source of truth for errors, traces, and infrastructure logs&lt;/li&gt;
&lt;li&gt;Sentry auto-detects &lt;a href=&quot;https://docs.sentry.io/product/issues/issue-details/performance-issues/n-one-queries/&quot;&gt;N+1 queries&lt;/a&gt;, slow spans, and performance regressions without manual configuration&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.sentry.io/product/ai-in-sentry/seer/&quot;&gt;Seer&lt;/a&gt;, Sentry&amp;#39;s AI debugger, can suggest a likely root cause for new issues automatically and hand off fixes to your coding agent&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The stack problem agents create&lt;/h2&gt;
&lt;p&gt;AI-assisted development has a specific failure mode: agents write working code that has no observability built in. You could end up with a Next.js app that talks to Supabase via three different connection methods (direct Postgres, the Supabase JS SDK, and Drizzle, because the agent kept switching strategies), edge functions running in Deno, and no unified view of what&amp;#39;s actually happening at runtime.&lt;/p&gt;
&lt;p&gt;The other failure mode is subtler. Agents forget indexes. They could end up writing N+1 queries that are invisible locally because your dev database has 40 rows. You ship, your database grows to 400 rows, and suddenly a search query takes ten seconds. Sentry catches this automatically, but only if it&amp;#39;s instrumented correctly from the start.&lt;/p&gt;
&lt;p&gt;Getting that instrumentation right requires understanding a few things about how Supabase and Sentry fit together.&lt;/p&gt;
&lt;h2&gt;Supabase&amp;#39;s built-in observability and its limits&lt;/h2&gt;
&lt;p&gt;Supabase has solid built-in observability. The &lt;em&gt;Query Performance&lt;/em&gt; panel in the dashboard shows which queries run most often and which consume the most time. That&amp;#39;s where you start when performance is the problem. The &lt;em&gt;Advisors&lt;/em&gt; surface security issues like missing RLS policies and rank them by severity. The &lt;em&gt;Index Advisor&lt;/em&gt; flags missing indexes before they become production incidents.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Logs&lt;/em&gt; section gives you structured logs from every Supabase subsystem: edge functions, the Postgres REST API (PostgREST), the connection pooler, storage, and cron jobs. You can query them with SQL directly in the dashboard.&lt;/p&gt;
&lt;p&gt;That&amp;#39;s genuinely useful. But it&amp;#39;s bounded by what Supabase can see, which is everything that happens inside Supabase. It can&amp;#39;t tell you that a slow Postgres query was triggered by a specific user action in your Next.js frontend, or that an edge function timeout caused a cascade of errors in your API layer. For that, you need &lt;a href=&quot;https://sentry.io/product/tracing/&quot;&gt;distributed tracing&lt;/a&gt; across the full stack.&lt;/p&gt;
&lt;h2&gt;Connecting Supabase logs to Sentry&lt;/h2&gt;
&lt;p&gt;The fastest way to get Supabase data into Sentry is the &lt;a href=&quot;https://docs.sentry.io/product/drains/supabase/&quot;&gt;log drain&lt;/a&gt;. In the Supabase dashboard, under &lt;em&gt;Logs &amp;gt; Drain&lt;/em&gt;, you add a destination and paste your Sentry data source name (DSN). All logs from that Supabase project start flowing into a corresponding Sentry project.&lt;/p&gt;
&lt;p&gt;A few things worth knowing about this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;#39;s currently all-or-nothing. You can&amp;#39;t filter by log level on the Supabase side before the drain&lt;/li&gt;
&lt;li&gt;Once logs are in Sentry, you can filter by severity (&lt;code&gt;severity:warn&lt;/code&gt;, &lt;code&gt;severity:error&lt;/code&gt;) in the Log Explorer&lt;/li&gt;
&lt;li&gt;Keep the log drain in its own Sentry project, separate from your Next.js app and your edge functions. This keeps the signal clean and makes it easier to set project-specific alerts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The reason to bother with this, beyond convenience, is that Sentry can correlate these infrastructure logs with traces from your application layer. When an edge function throws an error, you can see the full request path: Next.js page load → API route → edge function → Postgres query, with timing for each span.&lt;/p&gt;
&lt;p&gt;For a step-by-step walkthrough of this setup, see the &lt;a href=&quot;https://sentry.io/cookbook/setup-supabase-log-drain-monitoring/&quot;&gt;Supabase log drain monitoring recipe&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Instrumenting Next.js and Supabase Edge Functions&lt;/h2&gt;
&lt;p&gt;This is where most agent-generated setups go wrong. Next.js is a full-stack framework that runs in multiple runtimes: Node.js on the server, V8 in the browser, and potentially edge runtimes. Supabase Edge Functions run in Deno. These are not the same environment, and they need separate Sentry projects and separate SDK configurations.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cli.sentry.dev&quot;&gt;The Sentry CLI&lt;/a&gt; handles this detection automatically:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;npx sentry@latest init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the Next.js app, your &lt;code&gt;sentry.server.config.ts&lt;/code&gt; should include the Supabase integration to get automatic instrumentation of database queries:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;


const supabaseClient = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
);

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  tracesSampleRate: 0.1,
  integrations: [
    // Instruments Supabase queries as spans in your traces
    // so you can see exactly which DB calls are slow
    Sentry.supabaseIntegration(supabaseClient, Sentry, {
      tracing: true,
      breadcrumbs: true,
    }),
  ],
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without the Supabase integration, your traces will show that an API route was slow, but not which query caused it. With it, every Supabase SDK call becomes a named span with timing data. See the &lt;a href=&quot;https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/integrations/&quot;&gt;Next.js integrations docs&lt;/a&gt; for the full list of what&amp;#39;s available.&lt;/p&gt;
&lt;p&gt;For edge functions running in Deno, initialize Sentry at the top of each function before any other imports:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;

Sentry.init({
  dsn: Deno.env.get(&amp;quot;SENTRY_DSN&amp;quot;),
  tracesSampleRate: 1.0, // sample everything in edge functions; volume is usually low
});

Deno.serve(async (req) =&amp;gt; {
  return await Sentry.withIsolationScope(async () =&amp;gt; {
    // your handler code
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The reason for separate projects: when Sentry&amp;#39;s AI features (more on this below) analyze an issue, they work within a project&amp;#39;s context. Mixing Next.js errors with Deno errors and Postgres logs in a single project makes that analysis noisier and less useful.&lt;/p&gt;
&lt;h2&gt;Automatic detection: N+1 queries, slow spans, and Web Vitals&lt;/h2&gt;
&lt;p&gt;Once instrumented, Sentry starts surfacing issues you didn&amp;#39;t know to look for.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;N+1 queries&lt;/strong&gt; get detected automatically. If your code fetches a list of posts and then queries the database once per post to get comments, Sentry identifies the pattern and creates a performance issue. This is the kind of &amp;quot;logic&amp;quot; agents like to write constantly. It&amp;#39;s the natural way to express the functionality, and it&amp;#39;s invisible until you have real traffic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slow spans&lt;/strong&gt; appear in the Trace Explorer. You can see exactly which database query, API call, or server-side render is consuming time, with the full request context attached.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webvitals.com/&quot;&gt;&lt;strong&gt;Core Web Vitals&lt;/strong&gt;&lt;/a&gt; for the frontend (&lt;a href=&quot;https://webvitals.com/lcp&quot;&gt;LCP&lt;/a&gt;, &lt;a href=&quot;https://webvitals.com/inp&quot;&gt;INP&lt;/a&gt;, &lt;a href=&quot;https://webvitals.com/cls&quot;&gt;CLS&lt;/a&gt;) show up in the Next.js performance dashboard alongside your API latency and server transaction data. Having frontend and backend performance in one place makes it easier to figure out whether a slow page is a rendering problem or a slow API response.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a href=&quot;https://sentry.io/orgredirect/organizations/:orgslug/dashboards/?filter=onlyPrebuilt&amp;query=next.js&amp;sort=mostPopular&quot;&gt;prebuilt Next.js dashboard&lt;/a&gt; in Sentry covers most of what you need out of the box and doesn&amp;#39;t count against your dashboard quota.&lt;/p&gt;
&lt;h2&gt;Setting up agents to instrument correctly&lt;/h2&gt;
&lt;p&gt;Two things make the difference between an agent that instruments your app correctly and one that produces outdated, incomplete configuration.&lt;/p&gt;
&lt;h3&gt;MCPs over training data&lt;/h3&gt;
&lt;p&gt;Both Sentry and Supabase have Model Context Protocol (MCP) servers. When your coding agent has access to the Sentry MCP, it can query your actual issues, traces, and project configuration in real time instead of guessing based on training data that might be two years old. Sentry&amp;#39;s SDK has changed significantly, and agents without current context will often configure it as if it&amp;#39;s only for error monitoring, missing performance tracing entirely.&lt;/p&gt;
&lt;h3&gt;Skills files&lt;/h3&gt;
&lt;p&gt;For Claude Code, this is &lt;code&gt;.claude/&lt;/code&gt;. For Cursor and others, &lt;code&gt;.agents/&lt;/code&gt;. These files give your agent project-specific context that persists across sessions. Take a look at &lt;a href=&quot;https://docs.sentry.io/ai/agent-plugin/&quot;&gt;our Agent Skills documentation&lt;/a&gt; for a detailed breakdown of all the skills Sentry offers.&lt;/p&gt;
&lt;p&gt;A practical workflow: when you need to add Sentry to a project, go to the Sentry docs, find the SDK for your framework, copy the setup prompt they provide, and give that to your agent. The docs include current best practices and the right SDK version. Don&amp;#39;t just tell the agent to &amp;quot;add Sentry.&amp;quot; It will find a way to do it, and the result will probably work, but it won&amp;#39;t be right.&lt;/p&gt;
&lt;h2&gt;Monitoring beyond errors&lt;/h2&gt;
&lt;p&gt;Errors are the obvious case. But some of the most useful monitoring is for things that aren&amp;#39;t errors.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://sentry.io/product/logs/&quot;&gt;&lt;strong&gt;Log-based monitors&lt;/strong&gt;&lt;/a&gt; let you alert on patterns in your log stream. If you&amp;#39;re draining Supabase logs into Sentry, you can create a monitor that fires when the count of &lt;code&gt;connection received&lt;/code&gt; logs drops below a threshold in a given hour. Not an error, just a signal that something might be wrong with your database connectivity. In the Sentry UI: &lt;em&gt;Alerts &amp;gt; Create Alert &amp;gt; Logs&lt;/em&gt;, filter by message content, set a count threshold, and assign it to yourself or a team.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dynamic alerting&lt;/strong&gt; is useful when you don&amp;#39;t know your normal thresholds yet. Set an alert to use anomaly detection instead of a fixed value. Sentry&amp;#39;s ML figures out what &amp;quot;normal&amp;quot; looks like for your transaction response times and fires when something falls outside that pattern. Start with dynamic, tune to specific values once you understand your baseline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sentry CLI for dashboards:&lt;/strong&gt; The new &lt;a href=&quot;https://sentry.io/cookbook/create-dashboards-with-ai-agent/&quot;&gt;&lt;strong&gt;Sentry CLI&lt;/strong&gt; has a &lt;code&gt;dashboards&lt;/code&gt; command&lt;/a&gt; that an agent can use to build a custom dashboard from your actual trace data. Point it at your project, ask it to build a performance dashboard for your application, and it will inspect your active transactions and spans to figure out what&amp;#39;s worth visualizing. The output isn&amp;#39;t perfect (you&amp;#39;ll want to review widget configurations), but it&amp;#39;s a reasonable starting point that takes about thirty seconds instead of thirty minutes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Seer: from alert to fix&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://sentry.io/product/seer/&quot;&gt;Seer&lt;/a&gt; is Sentry&amp;#39;s AI debugger. It has access to your full issue history, traces, logs, and &lt;a href=&quot;https://sentry.io/product/session-replay/&quot;&gt;session replays&lt;/a&gt;. You can ask it plain questions: &amp;quot;which of my open issues are getting worse?&amp;quot; or &amp;quot;what are my slowest database queries?&amp;quot; and it will pull from your actual data to answer.&lt;/p&gt;
&lt;p&gt;The more interesting capability is &lt;a href=&quot;https://sentry.io/product/seer/autofix/&quot;&gt;Autofix&lt;/a&gt;. Configure it in your Sentry project settings by connecting your repository. When a new issue comes in, Seer automatically suggests a likely root cause and, if you want, generates a draft PR with a suggested fix. You can configure how far it goes: root cause only, or full fix with updated tests.&lt;/p&gt;
&lt;p&gt;For the Supabase security advisory workflow: the Supabase MCP exposes RLS policy issues and other advisories. An agent with both the Supabase MCP and the Sentry MCP can fetch those advisories and create Sentry issues from them, putting security problems into the same workflow as application errors. From there, Seer can pick them up and attempt fixes automatically.&lt;/p&gt;
&lt;p&gt;This is what &amp;quot;self-healing software&amp;quot; actually looks like in practice: not magic, but a pipeline where new issues get triaged, analyzed, and handed to a coding agent without you having to be the one who notices them first.&lt;/p&gt;
&lt;h2&gt;Where to start&lt;/h2&gt;
&lt;p&gt;The fastest path is three steps: run &lt;code&gt;npx sentry@latest init&lt;/code&gt; to instrument your Next.js app, add the Supabase integration to your server config for query-level spans. Then set up a log drain from Supabase into its own Sentry project. That gets you unified tracing across the full stack. From there, connect your repo to Seer and let it start suggesting fixes for new issues as they come in.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://sentry.io/integrations/supabase/&quot;&gt;Sentry Supabase integration docs&lt;/a&gt; cover setup end to end. Supabase has their own &lt;a href=&quot;https://supabase.com/docs/guides/telemetry/sentry-monitoring&quot;&gt;Sentry monitoring guide&lt;/a&gt; and a separate guide for &lt;a href=&quot;https://supabase.com/docs/guides/functions/examples/sentry-monitoring&quot;&gt;edge function monitoring&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Monitor Unreal Engine Game Performance with Application Metrics</title><link>https://blog.sentry.io/unreal-engine-performance-metrics/</link><guid isPermaLink="true">https://blog.sentry.io/unreal-engine-performance-metrics/</guid><description>The Unreal SDK now auto-instruments FPS, frame time, network health, and game stats, giving your team real player performance data in production.</description><pubDate>Fri, 08 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Your Unreal game can ship with zero errors and still not feel great. Stutters during combat, a frame-rate cliff on the big boss, rubber-banding in multiplayer, none of it shows up as a crash and none of it shows up in Sentry, leaving you without any visibility into what your players are actually experiencing in the wild. Well, until now.&lt;/p&gt;
&lt;p&gt;Unreal Engine already gives you plenty of tools to measure game performance and collect runtime stats, but all that data stays on the dev&amp;#39;s machine.&lt;/p&gt;
&lt;p&gt;The Unreal SDK&amp;#39;s new automatic performance metrics feature closes this gap by piping FPS, frame time, network health, and other common game telemetry straight to Sentry, so your team gets actionable insight into where performance breaks down, on which hardware, for which players. Pair it with &lt;a href=&quot;https://docs.sentry.io/platforms/unreal/configuration/releases/&quot;&gt;Release &amp;amp; Health&lt;/a&gt; and you can watch the performance impact of each release land over time.&lt;/p&gt;
&lt;p&gt;A quick note before we dig in: every gamedev has used a profiler at some point. Automatic performance metrics are a different-but-related tool, both go after the same problem at different layers: metrics find &lt;strong&gt;where&lt;/strong&gt; the game is slowing down, profiling explains &lt;strong&gt;why&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;What Sentry now tracks&lt;/h2&gt;
&lt;p&gt;Currently, Unreal SDK auto-instruments metrics for several key areas that impact overall performance including frame time, network and game-specific stats.&lt;/p&gt;
&lt;h3&gt;Frame time&lt;/h3&gt;
&lt;p&gt;The most direct read on whether your game feels responsive. Frame times tell you &amp;quot;how long the engine spent on each frame&amp;quot;; breaking it down by thread tells you which subsystem is the bottleneck.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Average FPS&lt;/li&gt;
&lt;li&gt;Total frame time&lt;/li&gt;
&lt;li&gt;Game thread work time&lt;/li&gt;
&lt;li&gt;Render thread work time&lt;/li&gt;
&lt;li&gt;GPU frame time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Comparing game thread vs render thread vs GPU time is the classic way to tell whether you&amp;#39;re CPU-bound or GPU-bound and which team (gameplay, rendering, content) owns the fix.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;FPS metric example (grouped by GPU)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Network insights&lt;/h3&gt;
&lt;p&gt;Multiplayer performance lives or dies by connection quality, and crash reporting can&amp;#39;t see any of it. These metrics tell you whether packet loss, latency or bandwidth starvation is quietly degrading the experience.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Incoming/outgoing bandwidth&lt;/li&gt;
&lt;li&gt;Packet throughput and loss&lt;/li&gt;
&lt;li&gt;Client ping and jitter&lt;/li&gt;
&lt;li&gt;Active connection count&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Server builds additionally get per-client ping averages, per-client bandwidth and saturated-connection counts for load-shedding analysis (see the &lt;a href=&quot;https://docs.sentry.io/platforms/unreal/metrics/#network&quot;&gt;full list of network metrics&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;These metrics only exist during active multiplayer sessions. Singleplayer games without networking emit nothing here and some values are client-only (ping, jitter) or server-only (active clients, saturation).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Ping metric example&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Game stats&lt;/h3&gt;
&lt;p&gt;A small grab-bag of engine-level signals that often explain hitches the frame-time breakdown alone can&amp;#39;t.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Number of active UObjects&lt;/li&gt;
&lt;li&gt;Physical memory used by the process&lt;/li&gt;
&lt;li&gt;Duration of the blocking GC pause&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A UObject count that climbs steadily between GCs is a classic leak signature and correlating it with GC pause duration often reveals exactly when a leak starts hurting player experience.&lt;/p&gt;
&lt;p&gt;Unlike frame time, these are sampled on a slower cadence: memory and object count every 60 seconds, GC pause emitted after each collection cycle. Values change slowly enough that per-frame resolution would be wasted throughput.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Used Memory metric example (grouped by platform, console-only)&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Sampling performance metrics&lt;/h2&gt;
&lt;p&gt;Emitting a metric every frame would be an overhead on its own. To avoid that, the SDK samples at a fixed interval, emitting one data point every N frames for per-frame metrics like frame time and FPS, and every N seconds for slower-changing ones like memory use or network health. The defaults are conservative and tunable per project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;~2 samples per second for frame time at 60 FPS&lt;/li&gt;
&lt;li&gt;Every 10 seconds for network&lt;/li&gt;
&lt;li&gt;Every 60 seconds for game stats&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On any single client this is sparse, a hitch on a non-sampled frame won&amp;#39;t be captured. But across many players the aggregate distribution converges on the real picture. You want to know &amp;quot;what&amp;#39;s the p95 frame time on RTX 3050 hardware?&amp;quot;, not &amp;quot;what did frame #47312 look like on dev&amp;#39;s laptop.&amp;quot; If you need tighter resolution simply dial the interval down.&lt;/p&gt;
&lt;h2&gt;Metrics attributes&lt;/h2&gt;
&lt;p&gt;An aggregate FPS number on its own doesn&amp;#39;t tell you much. What makes it useful is breaking it down: per GPU, per platform, per level. Every automatic metric is tagged with context attributes so you can do exactly that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GPU model name&lt;/li&gt;
&lt;li&gt;Number of CPU cores&lt;/li&gt;
&lt;li&gt;Total physical RAM&lt;/li&gt;
&lt;li&gt;Screen resolution&lt;/li&gt;
&lt;li&gt;Current game map/level name&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Metrics also carry the release version, operating system, and crucially the trace ID of whatever was happening when they were emitted. That last one is what separates metrics-in-Sentry from a standalone monitoring tool: spot a frame-time spike in the dashboard, click into the sample and you land in the full trace for that moment alongside any errors and spans captured with it.&lt;/p&gt;
&lt;p&gt;For example, group FPS (&lt;code&gt;game.perf.fps&lt;/code&gt;) by GPU (&lt;code&gt;gpu.name&lt;/code&gt;) and the answer to &amp;quot;what FPS do RTX 3080 players actually see versus RTX 3050?&amp;quot; is one query away. Swap the grouping to OS (&lt;code&gt;os.name&lt;/code&gt;) and you can compare memory footprint across Xbox, PlayStation and Switch.&lt;/p&gt;
&lt;h2&gt;Try it out and tell us what&amp;#39;s next&lt;/h2&gt;
&lt;p&gt;Automatic performance metrics are enabled by default in Unreal SDK &lt;a href=&quot;https://github.com/getsentry/sentry-unreal/releases&quot;&gt;1.11.0&lt;/a&gt;. See the &lt;a href=&quot;https://docs.sentry.io/platforms/unreal/metrics/#automatic-metrics&quot;&gt;Unreal SDK metrics docs&lt;/a&gt; for more on engine-version requirements and advanced configuration. Automatic metrics work on desktop, consoles and Android (with iOS support coming soon).&lt;/p&gt;
&lt;p&gt;Ship a build with automatic performance metrics enabled and let it run for a few sessions, that&amp;#39;s often enough to see whether hardware segmentation, frame-time percentiles or network health are already surfacing something worth fixing.&lt;/p&gt;
&lt;p&gt;And since the feature is still experimental, what gets measured next is up for grabs. If there&amp;#39;s a signal you wish we were capturing, open an issue on the &lt;a href=&quot;https://github.com/getsentry/sentry-unreal/issues&quot;&gt;Unreal SDK repo&lt;/a&gt;, as that&amp;#39;s the best way to shape where this goes.&lt;/p&gt;
&lt;p&gt;Have questions or feedback?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Join the conversation &lt;a href=&quot;https://discord.com/invite/sentry&quot;&gt;in our Discord&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Email us at &lt;a href=&quot;mailto:gaming-updates@sentry.io&quot;&gt;&lt;strong&gt;gaming-updates@sentry.io&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;New to Sentry?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Try &lt;a href=&quot;https://sentry.io/signup/&quot;&gt;Sentry for free&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Fixing JavaScript observability, one library at a time</title><link>https://blog.sentry.io/fixing-javascript-observability/</link><guid isPermaLink="true">https://blog.sentry.io/fixing-javascript-observability/</guid><description>Sentry is adding TracingChannel support to 44 JavaScript libraries upstream, replacing fragile monkey-patching with native observability that works across all runtimes.</description><pubDate>Thu, 07 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Over the past few weeks, we have been driving a cross-ecosystem effort to replace the &amp;quot;monkey-patching&amp;quot; that powers all JavaScript APM tools today with something built into the runtime. Here is why, how, and where it stands.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This applies to server-side JavaScript only (Node.js, Bun, Deno, Cloudflare Workers). Browsers do not have &lt;code&gt;diagnostics_channel&lt;/code&gt; and lack the async context propagation primitives needed to polyfill it.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Monkey-patching does not scale&lt;/h2&gt;
&lt;p&gt;My teammate &lt;a href=&quot;https://github.com/s1gr1d&quot;&gt;Sigrid&lt;/a&gt; wrote a detailed breakdown of &lt;a href=&quot;https://blog.sentry.io/observability-with-tracing-channels/&quot;&gt;why monkey-patching is failing and how &lt;code&gt;TracingChannel&lt;/code&gt; solves it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The short version: every JavaScript APM tool, including Sentry&amp;#39;s, instruments libraries by intercepting &lt;code&gt;require()&lt;/code&gt; and &lt;code&gt;import&lt;/code&gt; calls at runtime using &lt;a href=&quot;https://github.com/nodejs/import-in-the-middle&quot;&gt;import-in-the-middle&lt;/a&gt; (IITM) and &lt;a href=&quot;https://github.com/nodejs/require-in-the-middle&quot;&gt;require-in-the-middle&lt;/a&gt; (RITM). This breaks with ECMAScript Modules (ESM), does not work in non-Node runtimes, conflicts with bundlers, and couples us to internal implementation details we do not control. The SDK also must load before the library it instruments, or instrumentation silently does nothing.&lt;/p&gt;
&lt;p&gt;This is not a Sentry-specific problem. Every APM vendor maintaining JavaScript instrumentation deals with the same fragility. The ecosystem is stuck.&lt;/p&gt;
&lt;p&gt;Most library maintainers do not think about observability. They do not know what they would need to expose, and adopting something like OpenTelemetry means taking on an implementation burden, not just adding a standard. APMs managed to patch their way around this for years, so nobody on the library side ever had to figure it out.&lt;/p&gt;
&lt;p&gt;But there&amp;#39;s a better way.&lt;/p&gt;
&lt;h2&gt;TracingChannels - observability without patching&lt;/h2&gt;
&lt;p&gt;In late 2025, we were working with &lt;a href=&quot;https://github.com/pi0&quot;&gt;Pooya Parsa&lt;/a&gt; (creator of Nitro, h3, and the unjs ecosystem) on the best way to build a Sentry SDK for the Nitro framework. During that conversation, my teammate Sigrid suggested we look into &lt;a href=&quot;https://nodejs.org/api/diagnostics_channel.html#tracingchannel&quot;&gt;TracingChannel&lt;/a&gt;, a built-in API from Node&amp;#39;s &lt;code&gt;diagnostics_channel&lt;/code&gt; module. Sigrid&amp;#39;s &lt;a href=&quot;https://blog.sentry.io/observability-with-tracing-channels/&quot;&gt;blog post&lt;/a&gt; covers that API in depth, but the core idea is simple: if a library publishes structured events on a &lt;code&gt;TracingChannel&lt;/code&gt;, any APM tool can subscribe to those events without patching anything. The library just says &amp;quot;a query started&amp;quot; and &amp;quot;a query ended,&amp;quot; and whoever is listening can create spans from that.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Library side (e.g. inside mysql2)


const queryChannel = tracingChannel(&amp;#39;mysql2:query&amp;#39;);

queryChannel.tracePromise(async () =&amp;gt; {
  return await connection.query(sql);
}, { query: sql, serverAddress: host, serverPort: port });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The cost of this added code is minimal, so this is an easy sell for library maintainers. From APM&amp;#39;s side, we just need to subscribe to that tracing channel and we get the events. No IITM, no RITM, no loader hooks, no initialization ordering. Zero overhead when nobody is listening. Works across Node, Bun, and Deno. Bundler safe. The API has been available since Node 18, and &lt;a href=&quot;https://www.npmjs.com/package/dc-polyfill&quot;&gt;&lt;code&gt;dc-polyfill&lt;/code&gt;&lt;/a&gt; covers runtimes that lack it, which already matches our support range.&lt;/p&gt;
&lt;h2&gt;Everyone agrees, nobody is pushing&lt;/h2&gt;
&lt;p&gt;After getting enough learnings about the tracing channel API and how to make it work with OpenTelemetry, I opened &lt;a href=&quot;https://github.com/open-telemetry/opentelemetry-js/issues/6088&quot;&gt;an issue on Otel JS&lt;/a&gt; in November 2025 to discuss &lt;code&gt;TracingChannel&lt;/code&gt; support.&lt;/p&gt;
&lt;p&gt;The response was positive. A while after, someone from the OTel team even created a draft API approach for integrating &lt;code&gt;TracingChannel&lt;/code&gt; into the OTel SDK.&lt;/p&gt;
&lt;p&gt;But there is no significant push to drive ecosystem adoption. The draft exists; the ecosystem work does not.&lt;/p&gt;
&lt;p&gt;Everyone agrees that &lt;code&gt;TracingChannel&lt;/code&gt; is the future of JavaScript observability, but nobody is doing the work of getting libraries to adopt it. We have many instrumentations across databases, web frameworks, message queues, and AI providers that need &lt;code&gt;TracingChannel&lt;/code&gt; support. That is a mountain of upstream PRs, each requiring understanding the library&amp;#39;s internals, writing a proposal that maintainers will accept, implementing the changes, and iterating on review feedback.&lt;/p&gt;
&lt;p&gt;So I thought &amp;quot;fine, why not just get the ball rolling?&amp;quot;&lt;/p&gt;
&lt;p&gt;The first step was proving the pattern works. I had already built &lt;code&gt;TracingChannel&lt;/code&gt; support by hand in &lt;a href=&quot;https://github.com/h3js/h3/pull/1251&quot;&gt;h3&lt;/a&gt;, &lt;a href=&quot;https://github.com/h3js/srvx/pull/141&quot;&gt;srvx&lt;/a&gt;, &lt;a href=&quot;https://github.com/unjs/unstorage/pull/707&quot;&gt;unstorage&lt;/a&gt;, &lt;a href=&quot;https://github.com/unjs/db0/pull/193&quot;&gt;db0&lt;/a&gt;, and Nitro as part of the earlier SDK work. The unjs ecosystem was receptive and moved fast, which gave us shipped examples to point to and an end-to-end mental model: how events should be shaped, how context propagation flows, how to make it work with OTel, and what &lt;a href=&quot;https://opentelemetry.io/docs/specs/semconv/&quot;&gt;semantic conventions&lt;/a&gt; to follow.&lt;/p&gt;
&lt;p&gt;We also learned early that you can&amp;#39;t just say &amp;quot;hey you should use &lt;code&gt;TracingChannel&lt;/code&gt;,&amp;quot; which is just begging to be shelved to collect dust. Instead, like we did with Nitro, we say &amp;quot;Hey, we will do it for you and help you own it.&amp;quot; Accepting code into a repository adds a burden of maintenance, so we offer to help own it and make it part of the library.&lt;/p&gt;
&lt;p&gt;With that in mind, I reached out to &lt;code&gt;pg&lt;/code&gt;, &lt;code&gt;mysql2&lt;/code&gt;, and &lt;code&gt;redis&lt;/code&gt; to gauge their interest, offering to fully own this &amp;#39;til it ships and provide support even after. These are the top database driver libraries in the ecosystem, accounting for over 60 million downloads per week combined. If we can get &lt;code&gt;TracingChannel&lt;/code&gt; in them, we can get other libraries. All three said yes and were open to receiving a PR.&lt;/p&gt;
&lt;p&gt;I also reached out to &lt;a href=&quot;https://github.com/Qard&quot;&gt;Stephen Belanger&lt;/a&gt;, the creator of the &lt;code&gt;diagnostics_channel&lt;/code&gt; API in Node.js core. He is now helping push this forward, providing feedback on proposals and acting as the voice of authority which is sometimes needed to convince maintainers.&lt;/p&gt;
&lt;p&gt;So one by one, we&amp;#39;re making this happen across the ecosystem.&lt;/p&gt;
&lt;p&gt;For context on how this fits into the bigger picture: My team is working on making our SDK runtime-agnostic, we are working multiple paths in parallel, most of which have an immediate effect. The &lt;code&gt;TracingChannel&lt;/code&gt; initiative work is the long-term play. We cannot expect users to upgrade to new library versions overnight, and we probably won&amp;#39;t convince everyone to implement them at the same time so the migration will be gradual.&lt;/p&gt;
&lt;h2&gt;Scaling it with AI&lt;/h2&gt;
&lt;p&gt;Here is the practical reality: Being one person trying to add &lt;code&gt;TracingChannel&lt;/code&gt; support to 44 libraries is just not going to happen. I do not know the internals of any of them. I have never looked at the Redis protocol implementation or &lt;code&gt;mysql2&lt;/code&gt;&amp;#39;s query pipeline before this project.&lt;/p&gt;
&lt;p&gt;So I built a feedback loop using Claude Code that handles the per-library heavy lifting via SKILLS:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Research and Propose. Given a library name, Claude researches its async model, existing OTel instrumentation, maintenance status, and internal architecture, then drafts a proposal following all the patterns we have established. I review and adjust before it goes anywhere.&lt;/li&gt;
&lt;li&gt;Implement. Given an approved proposal, Claude produces a working implementation with tests, handling &lt;code&gt;tracePromise&lt;/code&gt;/&lt;code&gt;traceCallback&lt;/code&gt; selection, &lt;code&gt;hasSubscribers&lt;/code&gt; guards, Node 18 compatibility, and integration tests against real services via Docker.&lt;/li&gt;
&lt;li&gt;Capture Review Feedback. When a PR gets reviewed upstream, Claude triages every comment, assesses validity, suggests responses, and flags patterns that should inform future proposals. I decide what to act on and handle all communication with maintainers myself.&lt;/li&gt;
&lt;li&gt;Update the Tracker. Claude fetches the latest status of every upstream PR and keeps the migration tracker current.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each cycle feeds the next one. Learnings from one library&amp;#39;s review process improve the next library&amp;#39;s proposal. The knowledge compounds and is dumped into a &lt;code&gt;LEARNING.md&lt;/code&gt; file to guide future work.&lt;/p&gt;
&lt;p&gt;To clarify the human/AI split: Claude handles research, boilerplate implementation, and pattern application. I handle architecture decisions, insertion point identification, all maintainer communication, and final review of every line before it ships. Critically, every commit is co-authored and AI involvement is made transparent. Library maintainers interact with a human, not with an AI. I kept certain parts human-led because that shows respect to the maintainer&amp;#39;s work, which is critical to convincing them to adopt code into their library.&lt;/p&gt;
&lt;p&gt;This approach turned what would be a multi-year solo effort into a production line where I can keep dishing out proposals every day, start implementations in parallel, learn from them all and integrate the learnings into pending and future work.&lt;/p&gt;
&lt;h2&gt;10 merged, 34 to go&lt;/h2&gt;
&lt;p&gt;We are tracking many instrumentations across four categories. Here is where things stand:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;Category&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;Total&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;Merged&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;PR Open&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;In Discussion&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;Not Started&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;OTel-provided&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;24&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;4&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;6&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Sentry-built&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;10&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;0&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;0&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Other ecosystem&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;8&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;5&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Logging&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;0&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;0&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Total&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;44&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;10&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;4&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;8&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Notable wins:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sidorares/node-mysql2/pull/4178&quot;&gt;mysql2&lt;/a&gt; - Merged. One of the most popular database drivers in the npm ecosystem.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/redis/node-redis/pull/3195&quot;&gt;node-redis&lt;/a&gt; and &lt;a href=&quot;https://github.com/redis/ioredis/pull/2089&quot;&gt;ioredis&lt;/a&gt; - Both merged. The two dominant Redis clients now ship &lt;code&gt;TracingChannel&lt;/code&gt; support.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/h3js/h3/pull/1251&quot;&gt;h3&lt;/a&gt;, &lt;a href=&quot;https://github.com/h3js/srvx/pull/141&quot;&gt;srvx&lt;/a&gt;, &lt;a href=&quot;https://github.com/unjs/unstorage/pull/707&quot;&gt;unstorage&lt;/a&gt; - All merged. The unjs ecosystem was early and enthusiastic. This touches Nitro, which in turn touches Nuxt and other downstream frameworks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We also helped establish ecosystem coordination through an &lt;a href=&quot;https://github.com/e18e/ecosystem-issues/issues/255&quot;&gt;e18e umbrella issue&lt;/a&gt; and the &lt;a href=&quot;https://github.com/unjs/untracing&quot;&gt;&lt;code&gt;untracing&lt;/code&gt;&lt;/a&gt; spec that standardizes &lt;code&gt;TracingChannel&lt;/code&gt; usage for library authors.&lt;/p&gt;
&lt;h2&gt;What this means for Sentry&lt;/h2&gt;
&lt;p&gt;This flips the instrumentation model. Libraries own the contract, and we subscribe to it. Every problem described above (ESM breakage, init ordering, runtime lock-in, bundler conflicts) goes away. Our instrumentation code gets simpler, and we stop maintaining runtime-specific hacks.&lt;/p&gt;
&lt;p&gt;This also benefits every APM tool, not just Sentry. Driving it builds trust with library maintainers and the broader community, sure, but several maintainers have specifically called out that they appreciate the approach because it helps everyone and is not biased towards any one APM provider.&lt;/p&gt;
&lt;h2&gt;The flywheel is starting&lt;/h2&gt;
&lt;p&gt;Take &lt;code&gt;node-redis&lt;/code&gt; as a case study. During our collaboration with the Redis team, they were already working on their own first-party OpenTelemetry instrumentation. They wanted our &lt;code&gt;TracingChannel&lt;/code&gt; proposal to align with and power that instrumentation. We re-implemented their already shipped metrics plugin using tracing channels and it worked without changing a single test. Now, we are &lt;a href=&quot;https://github.com/redis/node-redis/pull/3218&quot;&gt;helping them with traces&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Shortly after &lt;code&gt;mysql2&lt;/code&gt; shipped &lt;code&gt;TracingChannel&lt;/code&gt; support, someone independently built &lt;a href=&quot;https://github.com/Vunovati/mysql2-otel-instrumentation&quot;&gt;&lt;code&gt;mysql2-otel-instrumentation&lt;/code&gt;&lt;/a&gt;, a pure &lt;code&gt;diagnostics_channel&lt;/code&gt; subscriber that replaces OTel&amp;#39;s monkey-patched &lt;code&gt;@opentelemetry/instrumentation-mysql2&lt;/code&gt;. The motivation was exactly the problem we are solving: RITM was not working. A library adds &lt;code&gt;TracingChannel&lt;/code&gt; support, and the subscribers manifest on their own.&lt;/p&gt;
&lt;h2&gt;What&amp;#39;s next&lt;/h2&gt;
&lt;p&gt;We have open PRs against Express, PostgreSQL (pg), Knex, and GraphQL, the kind of libraries where &lt;code&gt;TracingChannel&lt;/code&gt; support means millions of applications get better observability without changing a line of their own code. MongoDB, Mongoose, Prisma, and Hono are in active discussion, and we have drafted proposals for Koa and Consola. There are still 20+ libraries on the list we have not reached out to yet, including Node&amp;#39;s built-in HTTP module, Kafka clients, and AI provider SDKs.&lt;/p&gt;
&lt;p&gt;Beyond individual library adoption, the next layer is reducing duplication on the consumer side. Right now, every APM tool that subscribes to a &lt;code&gt;TracingChannel&lt;/code&gt; has to independently map library payloads to OpenTelemetry semantic conventions. We are designing a shared mapper registry, a set of co-maintained modules that translate &lt;code&gt;TracingChannel&lt;/code&gt; events into standardized spans and attributes. The goal is to build and prove this internally at Sentry first, then open-source it so any APM vendor can plug in. If a library ships &lt;code&gt;TracingChannel&lt;/code&gt; support and a mapper exists, instrumentation becomes automatic.&lt;/p&gt;
&lt;p&gt;The long-term picture is an ecosystem where libraries emit events as a first-class concern, mappers are community-maintained, and APM tools compete on what they do with the data rather than on how creatively they can patch your dependencies. We are not there yet, but the flywheel is turning.&lt;/p&gt;
&lt;p&gt;You can help by talking about tracing channels and advocating for their adoption in the libraries you use. If you maintain a library and want to add &lt;code&gt;TracingChannel&lt;/code&gt; support, the &lt;a href=&quot;https://github.com/unjs/untracing&quot;&gt;untracing&lt;/a&gt; conventions and our &lt;a href=&quot;https://github.com/getsentry/js-tracing-channels-proposals/&quot;&gt;published proposals&lt;/a&gt; are a good starting point.&lt;/p&gt;
</content:encoded></item><item><title>Improved debugging for Expo apps with the React Native SDK</title><link>https://blog.sentry.io/debugging-expo-react-native-sdk/</link><guid isPermaLink="true">https://blog.sentry.io/debugging-expo-react-native-sdk/</guid><description>Sentry&apos;s React Native SDK now gives Expo apps OTA update context, emergency launch detection, EAS build hooks, and navigation performance spans.</description><pubDate>Wed, 06 May 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Events from Expo apps account for about 75% of the total event volume we receive from React Native apps. That number made it an easy decision to invest in updates to the Sentry React Native SDK to improve the debugging and performance workflow for your Expo apps.&lt;/p&gt;
&lt;p&gt;With these updates, you can now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Filter issues by OTA update channel or version to instantly narrow down whether a problem is tied to a specific update&lt;/li&gt;
&lt;li&gt;Get alerted on emergency launches so you know when your OTA pipeline is failing before users report it&lt;/li&gt;
&lt;li&gt;Track EAS Build health in Sentry so you don&amp;#39;t have to dig through build logs to find out what broke&lt;/li&gt;
&lt;li&gt;See the full picture of navigation performance including prefetch timing and asset loading&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Automatic OTA update context on every event&lt;/h2&gt;
&lt;p&gt;When you ship over-the-air updates with Expo Updates, things can go wrong in ways that are invisible without the right context. Which update channel was the user on? Which runtime version? Was this the embedded bundle or a downloaded update?&lt;/p&gt;
&lt;p&gt;Now, every Sentry event is automatically enriched with an &lt;code&gt;ota_updates&lt;/code&gt; context, with no other setup required. You get the update ID, channel, runtime version, launch duration, and whether the app is using embedded assets. All of this is captured out of the box in Expo projects.&lt;/p&gt;
&lt;p&gt;We also set searchable tags (&lt;code&gt;expo.updates.channel&lt;/code&gt;, &lt;code&gt;expo.updates.runtime_version&lt;/code&gt;, &lt;code&gt;expo.updates.update_id&lt;/code&gt;) on every event, so you can filter your issue stream down to a specific channel or update with a single search query.&lt;/p&gt;
&lt;h2&gt;Emergency launch detection&lt;/h2&gt;
&lt;p&gt;Expo Updates performs an emergency launch when it fails to load the latest OTA update and falls back to the embedded bundle. When this happens, your users are silently running an older version of your app, and you might not even know.&lt;/p&gt;
&lt;p&gt;The SDK now detects emergency launches at startup and automatically sends a warning-level event to Sentry with the reason. You can set up an alert on the &lt;code&gt;expo.updates.emergency_launch&lt;/code&gt; tag and know immediately when your update pipeline is broken in production.&lt;/p&gt;
&lt;h2&gt;EAS build hooks: Track build failures in Sentry&lt;/h2&gt;
&lt;p&gt;Build failures in EAS Build happen on remote infrastructure, outside your local environment and outside your app. Until now, debugging them meant digging through EAS build logs manually.&lt;/p&gt;
&lt;p&gt;With the new EAS Build Hooks, you can send build lifecycle events directly to Sentry. Add three script entries to your &lt;code&gt;package.json&lt;/code&gt; (or just one if you prefer the combined &lt;code&gt;on-complete&lt;/code&gt; hook) and every failed build will send an &lt;code&gt;EASBuildError&lt;/code&gt; event with the build platform, profile, build ID, git commit hash, and CI status.&lt;/p&gt;
&lt;p&gt;You can also capture successful builds to give you a complete picture of your build pipeline health right inside Sentry. All events are tagged with &lt;code&gt;eas.*&lt;/code&gt; tags for easy filtering and alerting.&lt;/p&gt;
&lt;p&gt;Setup is minimal: point the hook scripts at the ones the SDK provides, set your DSN as an EAS secret, and you&amp;#39;re done. Check out the &lt;a href=&quot;https://docs.sentry.io/platforms/react-native/guides/expo/manual-setup/expo/eas-build-hooks/&quot;&gt;EAS Build Hooks documentation&lt;/a&gt; for setup instructions.&lt;/p&gt;
&lt;h2&gt;Performance spans for Expo Router prefetching&lt;/h2&gt;
&lt;p&gt;Expo Router v5 introduced &lt;code&gt;router.prefetch()&lt;/code&gt; to preload routes before the user navigates to them. It&amp;#39;s a great tool for perceived performance, but until now, prefetch timing was invisible in your traces.&lt;/p&gt;
&lt;p&gt;Wrapping your router with &lt;code&gt;Sentry.wrapExpoRouter(useRouter())&lt;/code&gt; now creates a &lt;code&gt;navigation.prefetch&lt;/code&gt; span for each prefetch call. You can see exactly how long route preloading takes alongside your other navigation spans, and identify routes where prefetching is slow or unnecessary.&lt;/p&gt;
&lt;h2&gt;Expo constants and environment context&lt;/h2&gt;
&lt;p&gt;Every event from an Expo app is automatically enriched with an &lt;code&gt;expo_constants&lt;/code&gt; context containing metadata about the execution environment: where the app is running (Expo Go, standalone, bare), the app name and version from &lt;code&gt;app.json&lt;/code&gt;, Expo SDK version, EAS project ID, and debug mode status.&lt;/p&gt;
&lt;p&gt;Combined with the OTA updates context, this gives you a complete picture of the environment for every event without writing a single line of configuration code.&lt;/p&gt;
&lt;h2&gt;Image and asset loading instrumentation&lt;/h2&gt;
&lt;p&gt;Image and asset loading is one of the biggest contributors to how fast your app feels. We&amp;#39;ve added automatic performance spans for two of the most common Expo packages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;expo-image&lt;/code&gt;: Wrap Image with &lt;code&gt;Sentry.wrapExpoImage(Image)&lt;/code&gt; once at startup, and every &lt;code&gt;Image.prefetch()&lt;/code&gt; and &lt;code&gt;Image.loadAsync()&lt;/code&gt; call gets a performance span.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;expo-asset&lt;/code&gt;: Wrap Asset with &lt;code&gt;Sentry.wrapExpoAsset(Asset)&lt;/code&gt; for spans on &lt;code&gt;Asset.loadAsync()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both wrappers are safe to call multiple times, create spans only when a trace is active (zero overhead otherwise), and don&amp;#39;t require expo-image or expo-asset to be installed. They&amp;#39;re peer dependencies.&lt;/p&gt;
&lt;h2&gt;Getting started&lt;/h2&gt;
&lt;p&gt;All of the OTA update and constants context is enabled by default. No configuration, no extra dependencies. For build hooks and performance instrumentation, setup is a few lines of code. Make sure you&amp;#39;re on &lt;a href=&quot;https://github.com/getsentry/sentry-react-native&quot;&gt;version 8.10&lt;/a&gt; to get the latest improvements and fixes.&lt;/p&gt;
&lt;p&gt;Expo also allows you to &lt;a href=&quot;https://expo.dev/blog/diagnose-and-debug-errors-faster-with-issues-and-replays-from-sentry-in-expo&quot;&gt;pull in issue details and replays from Sentry&lt;/a&gt; for errors occurring in your EAS deployments.&lt;/p&gt;
&lt;p&gt;Don&amp;#39;t have a Sentry account yet? &lt;a href=&quot;https://sentry.io/signup/&quot;&gt;Sign up for free&lt;/a&gt;, the developer plan includes everything you need to instrument an Expo app end to end. Have feedback on these integrations or ideas for what should come next? Open an issue on &lt;a href=&quot;https://github.com/getsentry/sentry-react-native&quot;&gt;&lt;code&gt;getsentry/sentry-react-native&lt;/code&gt;&lt;/a&gt; or drop into our &lt;a href=&quot;https://discord.gg/sentry&quot;&gt;Discord&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Introducing Application Metrics: Track the signal, see the spike, jump to the trace</title><link>https://blog.sentry.io/introducing-application-metrics/</link><guid isPermaLink="true">https://blog.sentry.io/introducing-application-metrics/</guid><description>We just launched Application Metrics, a new way to track critical signals in your application. It lets you understand your users with context and catch problems before they become errors.</description><pubDate>Tue, 05 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few weeks ago we had a bug with &lt;a href=&quot;https://docs.sentry.io/product/session-replay/&quot;&gt;Session Replay&lt;/a&gt;. Replays were failing in some browsers once more than 1,000 video segments loaded. We had no idea how often it happened or who was hitting it, and because the failure didn&amp;#39;t always produce an error, we had no way to find affected users to reproduce it.&lt;/p&gt;
&lt;p&gt;Before, we could&amp;#39;ve answered this with spans or logs, but it&amp;#39;s clunky — spans are often sampled, so you can miss outliers; logs are less structured and tend to change over time. Both are better suited for investigation. Metrics are ideal for tracking known behaviors over time. So we set up a metric in the Sentry SDK with a user and provider attribute, filtered for sessions over 1,000 segments, and had a repro case in minutes.&lt;/p&gt;
&lt;p&gt;That&amp;#39;s the job &lt;a href=&quot;https://sentry.io/product/metrics/&quot;&gt;Application Metrics&lt;/a&gt; is for: track the signals you care about, and attach the context you might need later. When something breaks, the data is already there waiting.&lt;/p&gt;
&lt;h2&gt;Full events, not pre-aggregated counters&lt;/h2&gt;
&lt;p&gt;Metrics tools designed for tracking infrastructure telemetry tend to aggregate, stripping out information like user, IP address, and region. They&amp;#39;re just a counter.&lt;/p&gt;
&lt;p&gt;Sentry&amp;#39;s Application Metrics stores full events, including high-cardinality fields like user. So you&amp;#39;re able to ask not just &amp;quot;was the checkout experience slow in my application?&amp;quot;, but &amp;quot;was the checkout experience slow for users on the east coast?&amp;quot;, or &amp;quot;was a specific user&amp;#39;s scheduled job causing a queue backlog?&amp;quot;&lt;/p&gt;
&lt;h2&gt;Same SDK, one line of code&lt;/h2&gt;
&lt;p&gt;If you&amp;#39;re on a recent Sentry SDK, metrics are already enabled. No new dependencies or sidecar — just one more line.&lt;/p&gt;
&lt;p&gt;There are three types you&amp;#39;ll reach for most:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Counter&lt;/strong&gt; — increment a number each time something happens. Think &lt;code&gt;payment.declined&lt;/code&gt;, &lt;code&gt;search.zero_results&lt;/code&gt;, or &lt;code&gt;email.failed&lt;/code&gt;. Good for tracking rates and totals you want to alert on.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Distribution&lt;/strong&gt; — record a value each time something happens, then ask questions about the spread. How long did that job take? How many items were in the queue? Use this when the average isn&amp;#39;t the whole story.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gauge&lt;/strong&gt; — track a current value over time. &lt;code&gt;queue.depth&lt;/code&gt;, &lt;code&gt;cache.size&lt;/code&gt;, &lt;code&gt;active.connections&lt;/code&gt;. The number you&amp;#39;d want on a dashboard.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can attach attributes to all three. That&amp;#39;s where Application Metrics differs from many infrastructure monitoring tools, which pre-aggregate and strip context. When you attach &lt;code&gt;user.id&lt;/code&gt;, &lt;code&gt;region&lt;/code&gt;, or &lt;code&gt;projectId&lt;/code&gt;, the event is stored with that context intact — so when a distribution spikes, you&amp;#39;re not just looking at a number, you&amp;#39;re looking at a number tied to a specific user, in a specific region, on a specific project.&lt;/p&gt;
&lt;h2&gt;Click a spike, see the trace&lt;/h2&gt;
&lt;p&gt;By storing full metrics events — including the trace ID — metrics become part of a broader trace-connected debugging workflow.&lt;/p&gt;
&lt;p&gt;When a metric reaches an unexpected threshold (a background job backing up with unsent emails; a UI component taking painfully long to load) you can jump from that metric to traces, logs, and errors, and get a full picture of what actually went wrong around the time your pager went off:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is a 429 error happening in a loop at the same time that a distribution measuring React component load times spikes?&lt;/li&gt;
&lt;li&gt;Is an upstream email service running slow at the same time that a gauge measuring queue depth increases?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;How we actually used this to find a Session Replay bug&lt;/h2&gt;
&lt;p&gt;To investigate the Session Replay problem, we began by adding a distribution that tracked the number of video segments loaded. We included the high-cardinality &lt;code&gt;projectId&lt;/code&gt; attribute.&lt;/p&gt;
&lt;p&gt;Here&amp;#39;s the code we added to start tracking video segments in replays:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const replayId = replay?.getReplay().id;
const projectId = replay?.getReplay().project_id;

const onLoadAllEvents = useEffectEvent(() =&amp;gt; {
  const attributes = {
    projectId: String(projectId),
    replayId,
  };

  Sentry.metrics.distribution(&amp;#39;replay.eventCount&amp;#39;, events?.length ?? 0, {
    attributes,
  });

  Sentry.metrics.distribution(&amp;#39;replay.videoEventCount&amp;#39;, videoEvents?.length ?? 0, {
    attributes,
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See &lt;a href=&quot;https://github.com/getsentry/sentry/pull/114001&quot;&gt;getsentry/sentry#114001&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We attached &lt;code&gt;replayId&lt;/code&gt; and &lt;code&gt;projectId&lt;/code&gt; as attributes on the &lt;a href=&quot;https://docs.sentry.io/platforms/javascript/enriching-events/scopes/&quot;&gt;scope&lt;/a&gt; so we could isolate high event counts to specific projects and replays. Given that we were having trouble reproducing the problem, this would help us catch the issue red-handed, tracing it back to a specific organization.&lt;/p&gt;
&lt;p&gt;With that in place, we quickly learned two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The issue was rare — just 7 occurrences in the past week.&lt;/li&gt;
&lt;li&gt;We had the exact users affected.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From there, we could trace those sessions, reproduce the issue, and fix it.&lt;/p&gt;
&lt;p&gt;Because each metric event carries a trace ID, we could go further. We added targeted logs to see exactly what the user was doing when &amp;gt;1,000 frames loaded — were they scrubbing the video, loading many videos in succession, etc. Next time we saw a &lt;code&gt;replay.videoEventCount&lt;/code&gt; over 1,000, we jumped to the connected trace, saw the log lines, and had the context to fix the bug.&lt;/p&gt;
&lt;h2&gt;Metrics vs. everything else&lt;/h2&gt;
&lt;p&gt;Metrics aren&amp;#39;t a replacement for &lt;a href=&quot;https://sentry.io/product/error-monitoring/&quot;&gt;errors&lt;/a&gt;, traces, or logs. They fill a specific gap: tracking interesting, well-understood events in your application with high fidelity.&lt;/p&gt;
&lt;p&gt;Not every event needs to be a metric. Logs are great during investigation. But when you find a signal you care about long-term — something that tracks application health — turn it into a metric.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Good candidates:&lt;/strong&gt; business KPIs tied to code execution (&lt;code&gt;payment.declined&lt;/code&gt;, &lt;code&gt;search.zero_results&lt;/code&gt;), application health indicators (&lt;code&gt;job.retried&lt;/code&gt;, &lt;code&gt;email.failed&lt;/code&gt;), resource utilization (&lt;code&gt;queue.depth&lt;/code&gt;, &lt;code&gt;cache.hit_rate&lt;/code&gt;), and success/failure rates you want to alert on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not the right fit:&lt;/strong&gt; infrastructure metrics like CPU and memory (use your infra tool), forensic debugging (use &lt;a href=&quot;https://sentry.io/product/logs/&quot;&gt;Sentry Logs&lt;/a&gt;), or request-level performance and connectivity (use &lt;a href=&quot;https://sentry.io/product/tracing/&quot;&gt;Sentry Tracing&lt;/a&gt;).&lt;/p&gt;
&lt;h2&gt;Start with the metric your team checks first&lt;/h2&gt;
&lt;p&gt;Every Sentry plan comes with 5GB of Application Metrics. If you&amp;#39;re on a recent SDK version, you already have access.&lt;/p&gt;
&lt;p&gt;Pick the one signal your team reaches for first when something goes wrong. Maybe it&amp;#39;s &lt;code&gt;checkout.failed&lt;/code&gt;, maybe it&amp;#39;s &lt;code&gt;queue.depth&lt;/code&gt;, maybe it&amp;#39;s &lt;code&gt;deployment.duration&lt;/code&gt;. Instrument it, attach the attributes you&amp;#39;d want to filter on — user, project, region, whatever matters for that metric — and set an alert threshold.&lt;/p&gt;
&lt;p&gt;When it fires, click through to the trace, find the context around the spike, and fix it.&lt;/p&gt;
&lt;p&gt;Start a free Application Metrics trial in &lt;strong&gt;&lt;a href=&quot;https://sentry.io/orgredirect/organizations/:orgslug/explore/metrics/&quot;&gt;Explore &amp;gt; Metrics&lt;/a&gt;&lt;/strong&gt;, or check out the &lt;a href=&quot;https://docs.sentry.io/product/metrics/&quot;&gt;Application Metrics docs →&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Two commands to Sentry: now on Stripe Projects</title><link>https://blog.sentry.io/sentry-stripe-projects/</link><guid isPermaLink="true">https://blog.sentry.io/sentry-stripe-projects/</guid><description>Sentry is now a provider in Stripe Projects. Provision error monitoring, upgrade plans, and open your dashboard from the CLI or from your coding agent.</description><pubDate>Wed, 29 Apr 2026 07:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Two commands. That&amp;#39;s how little it takes to go from nothing to a fully configured Sentry project with error monitoring, performance tracing, and session replay:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;stripe projects init my-app
stripe projects add sentry/project
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No signup form. No email verification dance. No dashboard tab-switching to copy-paste a DSN into your &lt;code&gt;.env&lt;/code&gt;. Your account is created, your project is provisioned, and five environment variables land in your working directory, ready for your SDK to pick up.&lt;/p&gt;
&lt;p&gt;And if you&amp;#39;re using a coding agent? It does the same thing, except you didn&amp;#39;t type the commands. You just said &amp;quot;add error monitoring.&amp;quot;&lt;/p&gt;
&lt;h2&gt;What this actually is&lt;/h2&gt;
&lt;p&gt;Sentry is now a provider in &lt;a href=&quot;https://projects.dev&quot;&gt;Stripe Projects&lt;/a&gt;. Stripe Projects is a CLI workflow that lets developers (and their AI agents) discover, provision, and manage infrastructure services directly from the terminal. Think of it as a package manager, but for the services your app depends on at runtime.&lt;/p&gt;
&lt;p&gt;Here&amp;#39;s the full catalog:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ stripe projects catalog sentry

SERVICES
    project   ● Free tier
    seer      ● Paid

PLANS
    developer ● Free
    team      ● $29/month
    business  ● $89/month
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two deployable services (a Sentry project and Seer AI), three plan tiers. All manageable from the CLI. Billing goes through your existing Stripe payment method, with no separate Sentry billing setup.&lt;/p&gt;
&lt;h2&gt;The &amp;quot;just tell your agent&amp;quot; part&lt;/h2&gt;
&lt;p&gt;When you run &lt;code&gt;stripe projects init&lt;/code&gt;, it scaffolds agent skill files into your project:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.agents/skills/stripe-projects-cli/SKILL.md
.claude/skills/stripe-projects-cli/SKILL.md
.cursor/rules/stripe-projects-cli.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These teach Claude Code, Cursor, or any coding agent how to use the Stripe Projects CLI. The agent reads the skill, discovers available services via &lt;code&gt;stripe projects catalog sentry --json&lt;/code&gt;, and provisions what you need.&lt;/p&gt;
&lt;p&gt;A real interaction looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;You: &amp;quot;Add error monitoring to this project&amp;quot;

Agent: [runs stripe projects catalog sentry --json]
Agent: [runs stripe projects add sentry/project --no-interactive --accept-tos]
Agent: &amp;quot;Done. Sentry is provisioned. Your DSN and auth token are in .env.
        I can integrate the Sentry SDK into your app next.&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The agent doesn&amp;#39;t need special Sentry knowledge. It just needs the Stripe Projects CLI and the skill file that &lt;code&gt;init&lt;/code&gt; already created. Provisioning becomes a step it handles between writing your code and running your tests.&lt;/p&gt;
&lt;p&gt;Once the account is provisioned, you can ask the agent to instrument your app with &lt;code&gt;sentry init&lt;/code&gt;. Since the auth token and DSN are already in the environment, the &lt;a href=&quot;https://cli.sentry.dev&quot;&gt;Sentry CLI&lt;/a&gt; knows exactly which project to target, with no configuration prompts, no guesswork.&lt;/p&gt;
&lt;h2&gt;Upgrades, downgrades, and the billing dance&lt;/h2&gt;
&lt;p&gt;Plans are first-class resources:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;stripe projects add sentry/team                          # start on Team ($29/mo)
stripe projects upgrade sentry-plan sentry/business      # upgrade to Business
stripe projects downgrade sentry-plan sentry/team        # change your mind
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All non-interactive, all scriptable. Billing happens through Stripe&amp;#39;s &lt;a href=&quot;https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens&quot;&gt;Shared Payment Token&lt;/a&gt;, so your existing Stripe payment method pays for Sentry with zero billing configuration on the Sentry side.&lt;/p&gt;
&lt;p&gt;You can also add Seer (our AI debugging assistant) as a separate service:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;stripe projects add sentry/seer      # $40/active contributor/month
stripe projects remove sentry-seer   # if you change your mind
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The magic login&lt;/h2&gt;
&lt;p&gt;This one&amp;#39;s my favorite. When you need your Sentry dashboard:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;stripe projects open sentry
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This mints a single-use magic login URL. Click it (or let the CLI open it), and you&amp;#39;re logged into your Sentry dashboard. You skip the password prompt, the single sign-on (SSO) redirect, and the &amp;quot;which account was this again?&amp;quot; moment. Straight to your issues page.&lt;/p&gt;
&lt;p&gt;It&amp;#39;s the kind of feature that sounds trivial but has a surprisingly dense security model once you start thinking about two-factor authentication (2FA) users, expired passwords, and SSO bypass prevention.&lt;/p&gt;
&lt;h2&gt;Multi-team collaboration&lt;/h2&gt;
&lt;p&gt;Here&amp;#39;s where it gets interesting. Stripe&amp;#39;s protocol distinguishes between the &lt;strong&gt;account owner&lt;/strong&gt; (the Stripe account&amp;#39;s email) and the &lt;strong&gt;actor&lt;/strong&gt; (the person running the CLI command). We use this to build a proper collaboration model:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First team member runs &lt;code&gt;stripe projects add sentry/project&lt;/code&gt; → creates the Sentry org&lt;/li&gt;
&lt;li&gt;Second team member runs the same command → joins the &lt;em&gt;same&lt;/em&gt; Sentry org&lt;/li&gt;
&lt;li&gt;Everyone shares one org, one billing setup, one set of projects&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We tie the Sentry organization to the Stripe organization, so everyone on the same Stripe account ends up in the same Sentry org. No per-developer silos, no &amp;quot;who created this and why can&amp;#39;t I see it&amp;quot; conversations.&lt;/p&gt;
&lt;h2&gt;Credential rotation&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;stripe projects rotate sentry-project
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;New DSN, new auth token, old ones revoked. The fresh credentials land in your &lt;code&gt;.env&lt;/code&gt; automatically. The dashboard stays closed.&lt;/p&gt;
&lt;h2&gt;How to try it&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install the Stripe CLI and the Projects plugin:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;stripe plugin install projects
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Initialize and add Sentry:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;stripe projects init my-app
stripe projects add sentry/project
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;That&amp;#39;s it. Your &lt;code&gt;SENTRY_DSN&lt;/code&gt; and &lt;code&gt;SENTRY_AUTH_TOKEN&lt;/code&gt; are in &lt;code&gt;.env&lt;/code&gt;, ready for any Sentry SDK to pick up.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For the full lifecycle (catalog browsing, plan management, Seer, deep links, credential rotation), check the &lt;a href=&quot;https://projects.dev&quot;&gt;Stripe Projects documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&amp;#39;re building with AI coding agents and want error monitoring that provisions itself, &lt;a href=&quot;https://projects.dev&quot;&gt;give it a try&lt;/a&gt;. And if your agent breaks something in the process, well, you&amp;#39;ll have Sentry to tell you about it.&lt;/p&gt;
</content:encoded></item><item><title>Sentry&apos;s integration with Perforce is now generally available</title><link>https://blog.sentry.io/perforce-integration-ga/</link><guid isPermaLink="true">https://blog.sentry.io/perforce-integration-ga/</guid><description>Sentry&apos;s Perforce P4 integration is now GA, bringing stack trace linking, suspect commits, and on-demand source context to game dev and VFX teams.</description><pubDate>Wed, 29 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Perforce meets Sentry&lt;/h2&gt;
&lt;p&gt;If you work in game development, VFX, or any industry dealing with large binary assets, chances are your codebase lives in Perforce P4. It&amp;#39;s the version control system behind some of the biggest games and creative projects in the world — and until now, it&amp;#39;s been one of the last major SCMs without first-class Sentry support.&lt;/p&gt;
&lt;p&gt;Today, we&amp;#39;re changing that. The Sentry + Perforce P4 integration is now generally available for all Sentry organizations.&lt;/p&gt;
&lt;h2&gt;What you get&lt;/h2&gt;
&lt;p&gt;The integration connects your P4 server directly to Sentry, unlocking the same source-code-aware debugging workflows that Git-based teams have relied on for years:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stack trace linking&lt;/strong&gt; — Click from an error&amp;#39;s stack trace directly to the corresponding file in your Perforce P4 depot or P4 Code Review (formerly Helix Swarm) instance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commit tracking&lt;/strong&gt; — Associate Perforce P4 changelists with Sentry releases so you always know exactly what code shipped.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Suspect commits&lt;/strong&gt; — Sentry automatically identifies which changelists likely introduced an error, cutting your triage time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Suggested assignees&lt;/strong&gt; — Get assignment recommendations based on changelist authorship — the person who last touched the code gets surfaced first.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P4 Code Review (formerly Swarm) linking&lt;/strong&gt; — If your team uses the P4 Code Review (formerly Helix Swarm) application for code reviews and browser-based depot browsing, Sentry links directly to your P4 Code Review instance. Stack trace links open the exact file in P4 Code Review&amp;#39;s web UI, so reviewers and investigators can jump from an error straight into the code without needing a local workspace.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;On-demand source context&lt;/h2&gt;
&lt;p&gt;The most requested capability during our beta: show me the code. Previously, showing the source code for native crashes required users to upload source maps to Sentry. Without source maps, showing the stacktraces was possible, but they didn&amp;#39;t have source context.&lt;/p&gt;
&lt;p&gt;With on-demand SCM source context, Sentry fetches source code directly from your Perforce P4 depot and displays it inline in the stack trace — even when your crash dumps or error reports don&amp;#39;t include embedded source. Expand any in-app frame and Sentry pulls the relevant lines on the fly, with the error line highlighted.&lt;/p&gt;
&lt;p&gt;This is especially valuable for native game development workflows where minidumps and crash reports rarely carry source context. Instead of switching to your IDE or running &lt;code&gt;p4 print&lt;/code&gt; manually, the code is right there in the issue.&lt;/p&gt;
&lt;h2&gt;How it works&lt;/h2&gt;
&lt;p&gt;Setup takes just a few minutes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Connect your server&lt;/strong&gt; — Go to Settings &amp;gt; Integrations &amp;gt; Perforce and enter your credentials.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configure code mappings&lt;/strong&gt; — Map your Sentry projects to Perforce P4 depots and set up path translations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enable source context (optional)&lt;/strong&gt; — Turn on SCM source context in your project&amp;#39;s General Settings to get inline code in stack traces.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sentry communicates with your Perforce P4 server using the P4Python library, executing lightweight read-only commands (&lt;code&gt;p4 depots&lt;/code&gt;, &lt;code&gt;p4 changes&lt;/code&gt;, &lt;code&gt;p4 print&lt;/code&gt;). Each organization maintains isolated credentials, and we support both password-based auth and pre-generated P4 tickets for LDAP environments.&lt;/p&gt;
&lt;h2&gt;How to get started&lt;/h2&gt;
&lt;p&gt;During beta, we worked closely with multiple game studios to battle-test the integration against real-world Perforce P4 deployments. We resolved concurrency challenges around P4 trust and ticket file isolation, ensuring connections stay clean and independent — whether you have one project or hundreds hitting the same or multiple servers.&lt;/p&gt;
&lt;p&gt;The Perforce integration is available now for all Sentry organizations.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.sentry.io/integrations/source-code-mgmt/perforce/&quot;&gt;Read the Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have questions or feedback?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Join the conversation &lt;a href=&quot;https://discord.com/invite/sentry&quot;&gt;in our Discord&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Email us at &lt;a href=&quot;mailto:gaming-updates@sentry.io&quot;&gt;gaming-updates@sentry.io&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;New to Sentry?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Try &lt;a href=&quot;https://sentry.io/signup/&quot;&gt;Sentry for free&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item></channel></rss>