<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rahul Chhabria</title>
    <description>The latest articles on DEV Community by Rahul Chhabria (@rahulchhabria).</description>
    <link>https://dev.to/rahulchhabria</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F599585%2F1410a7e9-4685-45c7-ab1f-3260479bc4fb.png</url>
      <title>DEV Community: Rahul Chhabria</title>
      <link>https://dev.to/rahulchhabria</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rahulchhabria"/>
    <language>en</language>
    <item>
      <title>Performance Monitoring and more updates to Sentry for Electron</title>
      <dc:creator>Rahul Chhabria</dc:creator>
      <pubDate>Fri, 25 Mar 2022 22:18:22 +0000</pubDate>
      <link>https://dev.to/sentry/performance-monitoring-and-more-updates-to-sentry-for-electron-17ni</link>
      <guid>https://dev.to/sentry/performance-monitoring-and-more-updates-to-sentry-for-electron-17ni</guid>
      <description>&lt;p&gt;For those who aren’t that familiar with it, Electron is an open-source framework that allows developers to build cross-platform desktop applications in JavaScript. Some of the most popular desktop applications like VS Code, Slack, Discord, and Atom, are all built in Electron.&lt;/p&gt;

&lt;p&gt;While Electron makes it easy to build cross-platform applications, maintaining them is a whole other thing. You need to be aware of OS-specific challenges, the machines your applications are running on, and their configurations. &lt;a href="https://docs.sentry.io/platforms/javascript/guides/electron/"&gt;Version 3.0 of our Electron SDK&lt;/a&gt; helps you get a handle on all these unknowns with more context on every error, insight into when a release starts to degrade, and enable &lt;a href="https://sentry.io/features/distributed-tracing/"&gt;distributed tracing&lt;/a&gt; to easily see where the most time in a transaction is spent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Using&lt;/span&gt; &lt;span class="nx"&gt;yarn&lt;/span&gt;
&lt;span class="nx"&gt;yarn&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;sentry&lt;/span&gt;&lt;span class="sr"&gt;/electro&lt;/span&gt;&lt;span class="err"&gt;n
&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Using&lt;/span&gt; &lt;span class="nx"&gt;npm&lt;/span&gt;
&lt;span class="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;sentry&lt;/span&gt;&lt;span class="sr"&gt;/electro&lt;/span&gt;&lt;span class="err"&gt;n
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Sentry&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@sentry/electron&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;Sentry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;dsn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://examplePublicKey@o0.ingest.sentry.io/0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;You need to call init in the main process and every renderer process you spawn.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With V3.0 of Sentry for Electron, you can automatically monitor application performance without any additional configuration. As well as, track crash-free sessions by release, and unlock more information for every issue like CPU details, display data, memory status, language details, and more. Check out the release notes &lt;a href="https://github.com/getsentry/sentry-electron/releases"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Electron Performance Monitoring
&lt;/h2&gt;

&lt;p&gt;Errors are only half the story. Electron developers also need to know if their app responds quickly to user input on any machine or operating system. This type of visibility helps you see if objects load quickly when called—and, if not, then provide you with tools to solve what’s urgent, faster.&lt;/p&gt;

&lt;p&gt;Sentry’s performance monitoring for Electron surfaces conditions that cause bottlenecks or latency issues for Electron renderer processes, along with a breakdown of operations within each transaction. So you can easily see which span is taking the longest and save yourself the frustration of clicking into every trace and span by getting operation details and insights on a silver platter.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/schaefferarnold11/embed/bGaqqWO?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Electron Release Health
&lt;/h2&gt;

&lt;p&gt;Version 3.0 automatically captures session data to help you better understand how each app release is performing. Sentry calculates crash-free sessions, crash-free users, and version adoption by release. If a release inexplicably tanks your crash-free users, you can see the moment a release starts to degrade and get a list of new issues and failed transactions. Plus, we also provide a list of commit authors so you can skip &lt;code&gt;git blame&lt;/code&gt; and just Slack your teammates who are best suited to fix the problem (that they created).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R2qukM2R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eknud5rc5kaxb20antl7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R2qukM2R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eknud5rc5kaxb20antl7.png" alt="Image description" width="880" height="669"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But hey, don’t know why there was a dramatic dip in crash-free sessions? This is fine. Click “Open in Discover” from any release details page; then, Sentry will automatically build and run a query for events by release to help you get to the root of any problem.&lt;/p&gt;

&lt;p&gt;Explore whether multiple conditions contribute to an issue: does this only happen on a specific machine or operating system? Or does it affect all Linux machines? Or all Windows 11 users? Answer it quickly in Discover.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2R1i-umM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ilj9sly3bx5cst4xzs4z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2R1i-umM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ilj9sly3bx5cst4xzs4z.png" alt="Image description" width="880" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Discover also includes pre-built searches that help answer common questions about all your events, unique errors, and clients. And if you don’t like these thoughtful, hand-crafted, pre-built searches, you can modify them and save them to a custom dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Electron Error Monitoring
&lt;/h2&gt;

&lt;p&gt;Understanding issues across various devices with access to different metadata, latencies, network issues, and upgrade cadences is hard and getting exponentially harder. Sentry for Electron 3.0 helps developers see what actually matters, solve issues faster with richer context, and learn continuously about their applications.&lt;/p&gt;

&lt;p&gt;Not only will you see the user impact of each issue, but you’ll also get all the information you need to know about the machines the error happened on, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU (processor count, machine architecture)&lt;/li&gt;
&lt;li&gt;Screen (density, resolution, height/width)&lt;/li&gt;
&lt;li&gt;Memory&lt;/li&gt;
&lt;li&gt;Language Details (locale details)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/schaefferarnold11/embed/GRyWWEd?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  To wrap it up
&lt;/h2&gt;

&lt;p&gt;Developers need visibility into when a user writes to disk, fetches media, loads an object in a given viewport—or, simply when a user tries to launch the app. Without this data, untreated performance problems and serious errors inevitably result in a poor user experience.&lt;/p&gt;

&lt;p&gt;Update your Electron SDK to immediately know everything about the hardware, OS, screen orientation, network conditions, and device language settings for every issue caught and reported by Sentry. Solve issues with context and confidence and save time by not looking for obscure machines to recreate the problem. With the latest Electron SDK and Sentry, you can easily uncover the who, what, when, where, and why behind every issue.&lt;/p&gt;

&lt;p&gt;Get started with &lt;a href="https://sentry.io/for/electron/"&gt;Sentry for Electron&lt;/a&gt; and drop us a line on &lt;a href="https://github.com/getsentry/sentry-javascript"&gt;GitHub&lt;/a&gt;, &lt;a href="https://twitter.com/getsentry?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor"&gt;Twitter&lt;/a&gt;, or our &lt;a href="https://discord.com/invite/sentry"&gt;Discord&lt;/a&gt;. And if you’re new to Sentry, you can try it for &lt;a href="https://sentry.io/orgredirect/try-business/"&gt;free&lt;/a&gt; today or write to &lt;a href="mailto:sales@sentry.io"&gt;sales@sentry.io&lt;/a&gt; to get started.&lt;/p&gt;

</description>
      <category>electron</category>
      <category>javascript</category>
      <category>performance</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Deprecation From U2F API to WebAuthn</title>
      <dc:creator>Rahul Chhabria</dc:creator>
      <pubDate>Wed, 19 Jan 2022 01:48:03 +0000</pubDate>
      <link>https://dev.to/sentry/deprecation-from-u2f-api-to-webauthn-1fkg</link>
      <guid>https://dev.to/sentry/deprecation-from-u2f-api-to-webauthn-1fkg</guid>
      <description>&lt;p&gt;By: &lt;a href="https://blog.sentry.io/authors/richard-ma"&gt;Richard Ma&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re using the U2F API for registration and authentication of your U2F devices, you will notice a dire situation is coming soon: Google Chrome will no longer support U2F API after February 2022:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---Ln37zk2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y4qpfv4rugc9b6mmpicv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---Ln37zk2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y4qpfv4rugc9b6mmpicv.png" alt="Image description" width="512" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For anyone using U2F API in their web apps, like us at Sentry, their users who had 2FA enabled with U2F devices would not be able to sign in. To remedy this, there’s a shiny new specification written by the World Wide Web Consortium (W3C) and the Fast IDentity Online Alliance (FIDO) that will solve all our problems.&lt;/p&gt;

&lt;p&gt;In this blog post, we will go into the weeds on migrating from U2F API to WebAuthn.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebAuthn
&lt;/h2&gt;

&lt;p&gt;WebAuthn is an API that allows web services to seamlessly integrate strong authentication into applications. With WebAuthn, web services can offer the user a choice of authenticators, such as security keys (Yubikeys, Titan Keys, for example) or built-in platform authenticators (biometric readers). In addition, it is supported by all the leading browsers — including Safari, which U2F API was not — and web platforms, which standardizes the integration of strong authentication. You can read more on &lt;a href="https://webauthn.io/"&gt;WebAuthn’s&lt;/a&gt; website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating to WebAuthn From U2F API
&lt;/h2&gt;

&lt;p&gt;Now, here’s the part you’re all here for, the migration to WebAuthn. Let’s break this down into two main parts:&lt;/p&gt;

&lt;p&gt;Part 1: Authenticating existing U2F and new WebAuthn devices with WebAuthn&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Step 1: Generating the challenge and state&lt;/li&gt;
&lt;li&gt;Step 2: Creating PublicKeyCredential data&lt;/li&gt;
&lt;li&gt;Step 3: Verifying the device&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Part 2: Registering new devices with WebAuthn&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Step 1: Generating the PublicKeyCredentialRpEntity and state&lt;/li&gt;
&lt;li&gt;Step 2: Creating PublicKeyCredential data&lt;/li&gt;
&lt;li&gt;Step 3: Registering the device&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 1: Authentication
&lt;/h2&gt;

&lt;p&gt;Let’s start with authentication so existing users can continue to log in. This is also important because newly registered WebAuthn devices can’t log in without a working WebAuthn login.&lt;/p&gt;

&lt;p&gt;To understand the authentication flow, we can look at the following diagrams:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--q7mdm5hN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b5obxfn5utpslmyby3hp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--q7mdm5hN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b5obxfn5utpslmyby3hp.jpg" alt="Image description" width="880" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[Source left][12] [Source right][13]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We’ll be using Python for the backend APIs. The &lt;a href="https://github.com/Yubico/python-u2flib-server"&gt;U2F API&lt;/a&gt; sequence (left) is very similar to the &lt;a href="https://github.com/Yubico/python-fido2"&gt;WebAuthn&lt;/a&gt; sequence (on the right). We simply have to replace three API calls: &lt;code&gt;u2f.start_authentication()&lt;/code&gt; and &lt;code&gt;u2f.finish_authentication()&lt;/code&gt; in the backend, and &lt;code&gt;u2f.sign()&lt;/code&gt; in the frontend.&lt;/p&gt;

&lt;p&gt;Let’s start with &lt;code&gt;u2f.start_authentication()&lt;/code&gt;, which takes in the browser’s application ID and the currently registered devices.&lt;/p&gt;

&lt;p&gt;The U2F API authentication process starts with the backend generating a challenge, an example of which is shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "appId": "https://your-webauthn-app.io/2fa/u2fappid.json",
  "challenge": "VwmGI-4…",
  "registeredKeys": [
      {
          "appId": "https://your-webauthn-app.io/2fa/u2fappid.json",
          "keyHandle": "cxSl4oQ…",
          "publicKey": "BP4Q8MR…",
          "transports": [
                "usb"
          ],
          "version": "U2F_V2"
      }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This challenge is sent to the browser where &lt;code&gt;u2f.sign()&lt;/code&gt; takes the challenge as an input and returns a promise that is the result of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;verifying the application identity of the caller&lt;/li&gt;
&lt;li&gt;creating a client data object and using the client data&lt;/li&gt;
&lt;li&gt;the application ID&lt;/li&gt;
&lt;li&gt;the key handle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a raw authentication request message and sends it to the U2F device. The result of the promise should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "keyHandle": "cxSl4oQ…",
    "clientData": "eyJ0eXA…",
    "signatureData": "AQAAAQ4…"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the result of the promise is sent to the server, we call &lt;code&gt;U2f.complete_authentication()&lt;/code&gt; with the following two parameters: the original challenge data and the newly generated client data object passed in. This method will verify the device with the parameters and return the device info if it succeeded. From there, the server can allow the user to pass through the 2FA process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Generating the challenge and state
&lt;/h3&gt;

&lt;p&gt;To start the migration process, let’s first replace &lt;code&gt;u2f.start_authentication()&lt;/code&gt; with its counterpart. The data types that the &lt;a href="https://github.com/Yubico/python-fido2"&gt;WebAuthn API&lt;/a&gt; takes are not quite the same ones used in U2F API. In fact, one of the main pain points was converting the necessary fields into the correct data type.&lt;/p&gt;

&lt;p&gt;We want to authenticate users on legacy U2F API and WebAuthn, so we will create an authentication server first. The following will create an authentication server using WebAuthn that is backwards compatible with U2F API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;webauthn_authentication_server = U2FFido2Server(
    app_id=u2f_app_id, 
    rp={
        "id": “sentry-webauthn.io”, 
        "name": "Sentry with WebAuthn"}
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your &lt;code&gt;app_id&lt;/code&gt; will be the same value as before. The &lt;code&gt;rp&lt;/code&gt;, or Relying Party, is an object that contains an ID, which is the hostname of the URL, and the name of your Relying Party.&lt;/p&gt;

&lt;p&gt;Next, we need to generate a list of credentials, which is the same as the list of devices for U2F API. Keep in mind that the list of credentials will contain both WebAuthn and U2F API registered devices and that list needs to be manipulated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;credentials = []

for device in self.get_u2f_devices():
    if type(device) == AuthenticatorData:
        credentials.append(device.credential_data)
    else:
        credentials.append(create_credential_object(device))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The devices that are registered with WebAuthn have the type AuthenticatorData. For devices registered with U2F API, we need to create an AttestedCredentialData object for them to be compatible with WebAuthn. The following is the function we wrote that decodes the necessary parameters and creates the credential data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def create_credential_object(registeredKey):
    return base.AttestedCredentialData.from_ctap1(
        websafe_decode(registeredKey["keyHandle"]),
        websafe_decode(registeredKey["publicKey"]),
    )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;[Source of function][16]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With that, we can begin the registration process by calling &lt;a href="https://developers.yubico.com/WebAuthn/"&gt;&lt;code&gt;register_begin()&lt;/code&gt;&lt;/a&gt; on the WebAuthn server that we created earlier, with credentials as its parameter. This will return a challenge and state.&lt;/p&gt;

&lt;p&gt;The challenge is needed for the browser to perform authentication, but we will only use the PublicKey object within the challenge. In addition, you should store the state in your sessions, as it will be needed later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;challenge, state = self.webauthn_authentication_server.authenticate_begin(
    credentials=credentials
)
request.session["webauthn_authentication_state"] = state
return ActivationChallengeResult(
    challenge=cbor.encode(challenge["publicKey"])
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also encoded the challenge using the [FIDO2 CBOR][18] library, as we will be sending it to the frontend using JSON, which does not handle binary representation well on its own. On the frontend, we convert the JSON string back into a byte array and decode it to return the challenge to its original form.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Creating PublicKeyCredential for authentication
&lt;/h3&gt;

&lt;p&gt;To replace &lt;code&gt;u2f.sign()&lt;/code&gt;, we can call its WebAuthn equivalent &lt;code&gt;navigator.credentials.get()&lt;/code&gt; with the challenge data. This library is now native to modern browsers, so don’t worry about importing any libraries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const challengeArray = base64urlToBuffer(
    challengeData.webAuthnAuthenticationData
);
const challenge = cbor.decodeFirst(challengeArray);

challenge.then(data =&amp;gt; {
    webAuthnSignIn(data);
}).catch(err =&amp;gt; {
    const failure = 'DEVICE_ERROR';
    Sentry.captureException(err);
    this.setState({
        deviceFailure: failure,
        hasBeenTapped: false,
    });
});

function webAuthnSignIn(publicKeyCredentialRequestOptions) {
    return navigator.credentials.get({
        publicKey: publicKeyCredentialRequestOptions,
    }).then(data =&amp;gt; {
        // Send to backend
    })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the promise is resolved after calling &lt;code&gt;navigator.credentials.get()&lt;/code&gt;, we need to send the appropriate data to the backend to finish authentication. To convert the PublicKeyCredential that was obtained from &lt;code&gt;navigator.credentials.get()&lt;/code&gt;, we can run it through the following function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getU2FResponse(data) {
    if (data.response) {
        const authenticatorData = {
          keyHandle: data.id,
          clientData: bufferToBase64url(data.response.clientDataJSON),
          signatureData: bufferToBase64url(data.response.signature),
          authenticatorData: bufferToBase64url(data.response.authenticatorData),
        };
        return JSON.stringify(authenticatorData);
    }

    return JSON.stringify(data);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Verifying the device
&lt;/h3&gt;

&lt;p&gt;For the final step, we can pass the original challenge and this new response to the backend. We need to create a list of credentials to validate the device, then call &lt;code&gt;authenticate_complete&lt;/code&gt; on the authentication server that was made earlier with the following parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;state: the value which we stored in session from start_authentication&lt;/li&gt;
&lt;li&gt;credentials: list which we just generated&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A websafe_decode for the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;credential_id&lt;/strong&gt;: a “keyHandle” of the response object&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;client_data&lt;/strong&gt;: a “clientData” of the response object passed through fido2.client.ClientData&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;auth_data&lt;/strong&gt;: an “authenticatorData” of the response object passed through fido2.ctap2.authenticatorData&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;signature&lt;/strong&gt;: a “signatureData” of the response object
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;self.webauthn_authentication_server.authenticate_complete(
    state=request.session["webauthn_authentication_state"],
    credentials=credentials,
    credential_id=websafe_decode(response["keyHandle"]),
    client_data=ClientData(websafe_decode(response["clientData"])),
    auth_data=AuthenticatorData(websafe_decode(response["authenticatorData"])),
    signature=websafe_decode(response["signatureData"]),
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this function returns true, you are now fully authenticated and good to go!&lt;/p&gt;

&lt;p&gt;For our deployment, this feature was behind a flag to manage the rollout and for error monitoring. (We recommend using Sentry 😉.) We deployed this feature independently from registration because the area of effect is limited in the event of an incident.&lt;/p&gt;

&lt;p&gt;Congrats! The authentication part of the migration is finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: Registration
&lt;/h2&gt;

&lt;p&gt;Similar to authentication, first, let’s take a look at the flow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4GQvOu6v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n00ro6pwq2x4k9j665yb.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4GQvOu6v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n00ro6pwq2x4k9j665yb.jpeg" alt="Image description" width="880" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[Source left][12] [Source right][20]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The flow of registration is almost identical to that of the authentication process. The three components we need to deprecate in order to migrate to WebAuthn are &lt;code&gt;u2f.begin_registration()&lt;/code&gt; and &lt;code&gt;u2f.complete_registration()&lt;/code&gt; in the backend, and &lt;code&gt;u2f.register()&lt;/code&gt; in the frontend.&lt;/p&gt;

&lt;p&gt;Once again, we will start with &lt;code&gt;u2f.begin_registration()&lt;/code&gt;. This API call takes in the u2f application ID and list of registered devices. This results in the following data being sent to the browser to begin the registration process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "appId": "https://your-webauthn-app/2fa/u2fappid.jso",
    "registerRequests": [
        {
            "challenge": "uexgFSl…",
            "version": "U2F_V2"
        }
    ],
    "registeredKeys": []
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to &lt;code&gt;u2f.sign()&lt;/code&gt;, &lt;code&gt;u2f.register()&lt;/code&gt; will take the previously generated results and return a promise that will look like the following if the device is registrable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "registrationData": "BQQ1xlC…",
    "version": "U2F_V2",
    "challenge": "Jkh_Tfo…",
    "appId": "https://your-webauthn-app.io/2fa/u2fappid.json",
    "clientData": "eyJ0eXA…"
} 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Along with the previously generated challenge, this result is sent to the backend where &lt;code&gt;u2f.complete_registration()&lt;/code&gt; takes in both parameters and generates the following data object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "appId": "https://your-webauthn-app.io/2fa/u2fappid.json",
    "keyHandle": "SnllNGC…",
    "publicKey": "BIs-gsW…",
    "transports": [
        "usb"
    ],
    "version": "U2F_V2"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can save this dictionary and name of the device. They will be used for authentication.&lt;/p&gt;

&lt;p&gt;Just like before, let’s replace &lt;code&gt;u2f.begin_registration()&lt;/code&gt; with its counterpart. We need to create a FIDO2Server and import it from &lt;code&gt;fido2.server&lt;/code&gt;. We don’t need to make it backward compatible, as all new devices will be registered with WebAuthn.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Generating the PublicKeyCredentialRpEntity and state
&lt;/h3&gt;

&lt;p&gt;We start with importing the &lt;a href="https://github.com/Yubico/python-fido2"&gt;&lt;code&gt;fido2.webauthn&lt;/code&gt; library&lt;/a&gt; to create a PublicKeyCredentialRpEntity. To create the entity, we need to pass in the Relying Party’s ID and name. With the entity, we pass it into Fido2Server to set things up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from fido2.server import Fido2Server
from fido2.webauthn import PublicKeyCredentialRpEntity

rp = PublicKeyCredentialRpEntity(rp_id, "Sentry")
webauthn_registration_server = Fido2Server(rp) 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the server is created, we can optionally pass in a list of registered devices to avoid duplicate registrations.&lt;/p&gt;

&lt;p&gt;Next, we call &lt;code&gt;register_begin()&lt;/code&gt; with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;user&lt;/strong&gt;: dictionary with the user’s id, name, and display name&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;credentials&lt;/strong&gt;: the list we just generated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;user_verification&lt;/strong&gt;: normally defaulted to discouraged&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should get a result similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  "publicKey": {
    "authenticatorSelection": {
      "userVerification": &amp;lt;UserVerificationRequirement.DISCOURAGED: "discouraged"&amp;gt;},
      "challenge": b"\xe9)#\x86\xfa.\xa9\x82r\x86\xf7\x15e\xb5m\xdc"
                   b"\x1dR\xc4\x1b\xdb\xab\x94\x88\xb8\x94\xf43"
                   b"b\x03\xab\n",
      "excludeCredentials": [],
      "pubKeyCredParams": [
        {"alg": -7,
         "type": &amp;lt;PublicKeyCredentialType.PUBLIC_KEY: "public-key"&amp;gt;},
        {"alg": -8,
         "type": &amp;lt;PublicKeyCredentialType.PUBLIC_KEY: "public-key"&amp;gt;},
        {"alg": -37,
         "type": &amp;lt;PublicKeyCredentialType.PUBLIC_KEY: "public-key"&amp;gt;},
        {"alg": -257,
         "type": &amp;lt;PublicKeyCredentialType.PUBLIC_KEY: "public-key"&amp;gt;}],
      "rp": {"id": "&amp;lt;$YOUR_APP&amp;gt;",
      "name": "Sentry"},
      "user": {"displayName": "&amp;lt;$YOUR_NAME&amp;gt;",
      "id": b"\x00",
      "name": "&amp;lt;$YOUR_APP&amp;gt;"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The registration data as seen above will be returned. Encode the registration data with the &lt;code&gt;cbor.encode()&lt;/code&gt; method and base64 encode that to a string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;publicKeyCredentialCreate = cbor.encode(registration_data)
return b64encode(publicKeyCredentialCreate)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the state for later use in the session.&lt;br&gt;
Interestingly, the data from WebAuthn is exactly the same from U2F API, despite looking different on first glance. WebAuthn sets the challenge in a byte array and the clientData in a COSE key object (which is a &lt;a href="https://www.jacobcasper.com/u2f2webauthn.html"&gt;CBOR map&lt;/a&gt;), while U2F API uses an encoded string.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: Creating PublicKeyCredential for registration
&lt;/h3&gt;

&lt;p&gt;Once the registration data is received by the browser, we convert the string into a buffer and decode it with &lt;a href="https://github.com/hildjj/node-cbor/tree/main/packages/cbor-web"&gt;this library&lt;/a&gt;. This gives us the data that will be used as the input parameter of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/create"&gt;&lt;code&gt;navigator.credentials.create()&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;challenge, state = self.webauthn_authentication_server.authenticate_begin(
    credentials=credentials
)
request.session["webauthn_authentication_state"] = state

return ActivationChallengeResult(challenge=cbor.encode(challenge["publicKey"]))

webAuthnRegister(publicKey) {
    const promise = navigator.credentials.create({publicKey});
    this.submitU2fResponse(promise);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Registering the device
&lt;/h3&gt;

&lt;p&gt;We have reached the final step where we need to extract some data from the response from &lt;code&gt;navigator.credentials.create()&lt;/code&gt;. The following are needed for &lt;code&gt;register_complete()&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;state&lt;/strong&gt;: from user sessions, set earlier after &lt;code&gt;begin_registration()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;client_data&lt;/strong&gt;: from decoding the data’s cliendDataJSON and creating a ClientData Object with it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AttestationObject&lt;/strong&gt;: from decoding the data’s attestationObject and creating an AttestationObject Object with it
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data = json.loads(response_data)
client_data = ClientData(
    websafe_decode(data["response"]["clientDataJSON"])
)
att_obj = base.AttestationObject(
    websafe_decode(data["response"]["attestationObject"])
)

binding = webauthn_registration_server.register_complete(
    state, client_data, att_obj
) 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ClientData should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "type": "webauthn.create",
    "challenge": "_Uas89Y…",
    "origin": "https://&amp;lt;$YOUR_APP&amp;gt;",
    "crossOrigin": false
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AttestationObject should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AttestationObject(
    fmt: 'none', 
    auth_data: AuthenticatorData(
        rp_id_hash: h'74cb1ce…5',
        flags: 0x41, 
        counter: 281, 
        credential_data: AttestedCredentialData(
            aaguid: h'0000000…', 
            credential_id: h'63af2c9…', 
            public_key: {...}
        ), 
        att_statement: {}, 
        ep_attr: None, 
        large_blob_key: None
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Registered device data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AuthenticatorData(
    rp_id_hash: h'74cb1ce…',
    flags: 0x41, 
    counter: 281, 
    credential_data: AttestedCredentialData(
        aaguid: h'0000000…', 
        credential_id: h'63af2c9…', 
        public_key: {...}
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that, you can save the registered device data. The registration process is complete.&lt;br&gt;
Just like authentication, the deployment of this feature was behind a feature flag to manage the rollout. There were no database migrations needed as WebAuthn is backward compatible with U2F API.&lt;/p&gt;

&lt;h2&gt;
  
  
  That’s a wrap
&lt;/h2&gt;

&lt;p&gt;With that, WebAuthn should be set up and you can purge U2F API from your codebase. If you have made it this far, we hope that this guide was useful to you. With some planning, you will make it in time before Chrome locks out users from your application. All the best!&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;

&lt;p&gt;Everything we do at &lt;a href="//www.sentry.io"&gt;Sentry&lt;/a&gt; is built in the open. Find us on &lt;a href="https://github.com/getsentry/sentry-mobile"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>news</category>
    </item>
    <item>
      <title>Bytecode Transformations: The Android Gradle Plugin</title>
      <dc:creator>Rahul Chhabria</dc:creator>
      <pubDate>Tue, 14 Dec 2021 23:05:18 +0000</pubDate>
      <link>https://dev.to/sentry/bytecode-transformations-the-android-gradle-plugin-4l6o</link>
      <guid>https://dev.to/sentry/bytecode-transformations-the-android-gradle-plugin-4l6o</guid>
      <description>&lt;p&gt;By: &lt;a href="https://github.com/romtsn/"&gt;Roman Zavarnitsyn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is the first part of a blog post series about bytecode transformations on Android. In this part we’ll cover different approaches to bytecode manipulation in Java as well as how to make it work with Android and the Android Gradle plugin. In the next two parts we’ll dive into the actual bytecode, bytecode instructions and how we can modify the bytecode and inject our own instructions, using &lt;a href="https://developer.android.com/jetpack/androidx/releases/room"&gt;Room&lt;/a&gt; as an example. In the last part we’ll see how we can test our transformations and how it can influence Gradle build speed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Detecting the slow spots in your app without having to write a single line of code is an intriguing idea, but is also not that easy to implement. At Sentry we wanted to provide the capability for Android users to automatically measure the execution time of database queries because oftentimes they can become a hidden bottleneck for app performance. &lt;a href="https://developer.android.com/jetpack/androidx/releases/room"&gt;Room&lt;/a&gt; is a widely-adopted ORM solution built by Google and is a go-to library for persistence for the majority of the Android developers, so it was an obvious choice for us to start with.&lt;/p&gt;

&lt;p&gt;If we want database operations to show up in Sentry, we need to wrap them into special objects called Spans. In short, Spans are application events that have a start and end time and some metadata like operation name, description, etc. For example, this is how the performance breakdown looks for a popular open-source app &lt;a href="https://github.com/chrisbanes/tivi"&gt;tivi&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cQo1BoWV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ja26ew7bbptsgaldx5bo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cQo1BoWV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ja26ew7bbptsgaldx5bo.png" alt="Image description" width="880" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the screenshot above, there’s a parent span &lt;code&gt;ui.screen.interaction&lt;/code&gt; that contains multiple child spans, for instance, the network request span with a &lt;code&gt;http.client&lt;/code&gt; operation, duration 225ms and a request url as a description. If you want to learn more about performance @ Sentry, check &lt;a href="https://blog.sentry.io/2021/08/23/mobile-vitals-four-metrics-every-mobile-developer-should-care-about#mobile-performance-monitoring"&gt;this post&lt;/a&gt; out.&lt;/p&gt;

&lt;p&gt;Back to the topic, this all means we need to find a way to inject our code before and after Room executes its queries to measure their execution time. There’s a &lt;a href="https://developer.android.com/reference/androidx/room/RoomDatabase.QueryCallback"&gt;QueryCallback&lt;/a&gt; available in the Room API, but it’s invoked only before a query gets executed, so we couldn’t really utilize it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Options
&lt;/h1&gt;

&lt;p&gt;Since there’s no way to know when a SQL query starts and finishes at runtime, we started looking into compile-time solutions. There are a few well-known options in the JVM world available for bytecode weaving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/AspectJ"&gt;AspectJ&lt;/a&gt;: an AOP framework, which allows extending methods and plugging into their execution from outside of the target codebase.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://asm.ow2.io/"&gt;ASM&lt;/a&gt;: a bytecode manipulation framework, which allows dealing with bytecode directly. For example, it’s used by R8 and D8 on Android for &lt;a href="https://jakewharton.com/digging-into-d8-and-r8/"&gt;optimizing and dexing&lt;/a&gt; the bytecode.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Other higher-level abstractions like &lt;a href="https://www.javassist.org/"&gt;Javassist&lt;/a&gt;: are all based on ASM, but have a nicer and easier-to-understand APIs to deal with.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While it would be logical to pick something higher-level, considering we had no expertise in any of those, we’ve decided to look into how we could marry those with the Android Gradle plugin (AGP), as we are aiming to transform Android apps and need to support things like differe build types, flavours and so on. A quick search revealed that we could go with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.transform.TransformAction.html"&gt;Gradle’s TransformAction&lt;/a&gt;: a plain Gradle API for transforming outputs. This is used, for example, for &lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/internal/dependency/DexingTransform.kt"&gt;dexing&lt;/a&gt;, &lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/internal/dependency/JetifyTransform.kt"&gt;jetifying&lt;/a&gt;, and many other things that the Android Gradle plugin does.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/transform/Transform"&gt;AGP’s Transform&lt;/a&gt;: an old API from AGP that gives a list of inputs to be transformed, depending on the options provided. It also handles full/incremental builds automatically. Now deprecated in favor of the new API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/variant/Component.html#transformclasseswith"&gt;AGP’s transformClassesWith&lt;/a&gt;: the new API from AGP that allows registering an ASM &lt;a href="https://asm.ow2.io/javadoc/org/objectweb/asm/ClassVisitor.html"&gt;ClassVisitor&lt;/a&gt; for visiting bytecode instructions and instrumenting &lt;code&gt;.class&lt;/code&gt; files. It utilizes the aforementioned &lt;a href="https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.transform.TransformAction.html"&gt;TransformAction&lt;/a&gt; to transform dependencies and provides a Gradle task that handles full/incremental builds automatically. Available from AGP version 4.2.0 and above.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first option would require us to manually hook into the AGP process and deal with its artifacts, so we decided to look into options 2 and 3 and compare them, as they come directly from the vendor.&lt;/p&gt;

&lt;p&gt;Previously, in the old AGP versions (pre-4.2.0), if one would like to instrument compiled classes, they would need to register their own &lt;a href="https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/transform/Transform"&gt;Transform&lt;/a&gt;, traverse the input files and perform instrumentation for each of those files using &lt;a href="https://asm.ow2.io/javadoc/org/objectweb/asm/ClassWriter.html"&gt;ClassWriter&lt;/a&gt; from ASM. For each such &lt;code&gt;Transform&lt;/code&gt; AGP would register a new Gradle task, so if you happen to have 10 transforms instrumenting your application, you would end up with 10 additional Gradle tasks doing almost the same thing - iterating over the changed files, reading the bytecode, applying their own transformations and writing the bytecode back.&lt;/p&gt;

&lt;p&gt;This is horrible for the build speed and most of that can be commonized up until the point of actually instrumenting the bytecode.&lt;/p&gt;

&lt;p&gt;The new &lt;code&gt;transformClassesWith&lt;/code&gt; API tackles exactly that by providing a single API for registering &lt;code&gt;ClassVisitors&lt;/code&gt; and abstracting away file iteration and reading/writing the bytecode. It collects all visitors in a single list and then, for each file, runs all of them in order of registering, meaning there’s just a single Gradle task running for all transformations.&lt;/p&gt;

&lt;p&gt;We’ve decided to go with ASM + &lt;code&gt;transformClassesWith&lt;/code&gt; pack, deliberately supporting only the new versions of AGP.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note, that if you want to support bytecode transformations in lower AGP versions (below 4.2.0) you still need to use the old Transform API. However, you can perform an AGP version check at runtime and choose either a new or an old API depending on it. An example can be seen in the &lt;a href="https://cs.android.com/android/platform/superproject/+/master:external/dagger2/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt;l=69-82"&gt;Hilt&lt;/a&gt; Gradle plugin.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Using new transform APIs
&lt;/h1&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Registering AsmClassVisitorFactory&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;As this post is not about how to create Gradle plugins, I will skip the setup part, but in a nutshell, we have to implement the &lt;code&gt;Plugin&lt;/code&gt; interface from Gradle and override a single method called &lt;code&gt;apply&lt;/code&gt;, which is called when the Gradle plugin is applied to a project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SentryPlugin&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Plugin&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that we have to listen when the Android Gradle plugin is applied to the project and retrieve the new &lt;a href="https://developer.android.com/reference/tools/gradle-api/4.2/com/android/build/api/extension/AndroidComponentsExtension"&gt;AndroidComponentsExtension&lt;/a&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pluginManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.android.application"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;androidComponentsExtension&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AndroidComponentsExtension&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The extension has a special &lt;code&gt;onVariants&lt;/code&gt; method that configures the build variants:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;androidComponentsExtension&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onVariants&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;variant&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally we can register our custom &lt;code&gt;AsmClassVisitorFactory&lt;/code&gt; for the &lt;code&gt;variant&lt;/code&gt; through &lt;code&gt;transformClassesWith&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transformClassesWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nc"&gt;SpanAddingClassVisitorFactory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nc"&gt;InstrumentationScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ALL&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tracingInstrumentation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;forceInstrumentDependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invalidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDisallowChanges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;currentTimeMillis&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDisallowChanges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tracingInstrumentation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tmpDir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmpDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;transformClassesWith&lt;/code&gt; accepts 3 parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ClassVisitorFactory&lt;/code&gt;: a factory, which provides a &lt;code&gt;ClassVisitor&lt;/code&gt; implementation and defines whether this visitor is interested in instrumenting a given class&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;InstrumentationScope&lt;/code&gt;: either &lt;code&gt;ALL&lt;/code&gt; or &lt;code&gt;PROJECT&lt;/code&gt;. Defines whether the instrumentation applies only for project files or for project files and their dependencies (e.g. jars). In our case, we were interested in instrumenting all Room queries, regardless of their origin, so we set it to &lt;code&gt;ALL&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;If you’re using &lt;code&gt;InsrumentationScope.ALL&lt;/code&gt;, beware that Gradle will cache the transformed artifacts across builds as long as the &lt;code&gt;InstrumentationParameters&lt;/code&gt; do not change. This may come as a surprise while developing, as some of the classes coming from the dependencies might not show up for instrumentation. We found it useful to have a boolean parameter, which would invalidate the transform caches by simply setting &lt;code&gt;System.currentTimeMillis&lt;/code&gt; and allow us to always receive all classes for instrumentation.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuration function to be applied before passing the necessary parameters for the &lt;code&gt;ClassVisitorFactory&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/instrumentation/InstrumentationParameters"&gt;InstrumentationParameters&lt;/a&gt; are the way to pass information from the plugin to the &lt;code&gt;ClassVisitorFactory&lt;/code&gt;. They are being used as Gradle inputs, this means they contribute to the up-to-date checks of the task and should be properly &lt;a href="https://docs.gradle.org/current/userguide/more_about_tasks.html#sec:up_to_date_checks"&gt;annotated&lt;/a&gt;. For example, here we are setting a &lt;code&gt;debug&lt;/code&gt; boolean as well as a &lt;code&gt;tmpDir&lt;/code&gt; to use this information later and stream debug output of instrumentation into a file under the &lt;code&gt;tmpDir&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Implementing AsmClassVisitorFactory&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For the &lt;code&gt;ClassVisitorFactory&lt;/code&gt; it’s necessary to implement 2 methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;createClassVisitor&lt;/code&gt; which provides a custom &lt;code&gt;ClassVisitor&lt;/code&gt; from ASM that does the actual visiting of bytecode instructions and transformation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;isInstrumentable&lt;/code&gt; which defines whether a given class is applicable for instrumentation or not&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is also necessary to specify an implementation of &lt;code&gt;InstrumentationParameters&lt;/code&gt; as a type for &lt;code&gt;AsmVisitorFactory&lt;/code&gt; or use &lt;code&gt;InstrumentationParameters.None&lt;/code&gt; in case there are no params.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SpanAddingClassVisitorFactory&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nc"&gt;AsmClassVisitorFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SpanAddingClassVisitorFactory&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SpanAddingParameters&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;SpanAddingParameters&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;InstrumentationParameters&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt;
        &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;Optional&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;invalidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;Internal&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tmpDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createClassVisitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;classContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ClassContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;nextClassVisitor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ClassVisitor&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ClassVisitor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"If we return true from the isInstrumentable below, we should return a ClassVisitor that will inject our code for measuring the execution time"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;isInstrumentable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ClassData&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Determine if we are interested in instrumenting the given ClassData. For us it would mean a class annotated with @Dao"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the &lt;code&gt;isInstrumentable&lt;/code&gt; method, we determine whether we are interested in instrumenting the given &lt;code&gt;ClassData&lt;/code&gt; and later return our custom &lt;code&gt;ClassVisitor&lt;/code&gt; from the &lt;code&gt;createClassVisitor&lt;/code&gt; method in case we are. Note, however, that it’s always a good practice to fall back to &lt;code&gt;nextClassVisitor&lt;/code&gt; in case there’s no &lt;code&gt;ClassVisitor&lt;/code&gt; for the given class, otherwise the Gradle build will fail.&lt;/p&gt;

&lt;p&gt;Last, let’s look at the &lt;code&gt;ClassData&lt;/code&gt; structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ClassData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * Fully qualified name of the class.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * List of the annotations the class has.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;classAnnotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * List of all the interfaces that this class or a superclass of this class implements.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;interfaces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * List of all the super classes that this class or a super class of this class extends.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;superClasses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It may seem to have everything to help us determine whether a class is suitable for instrumentation or not, but there’s a setback which we’ll cover in the next post.&lt;/p&gt;

&lt;p&gt;Using the new AGP transform APIs with ASM looks like an obvious choice for bytecode manipulation for Android as it affects the build speed almost unnoticeable (we’ll cover that later), handles full/incremental builds on its own, and offers a great API surface via ASM at the same time.&lt;/p&gt;

&lt;p&gt;In the next part, we’ll talk about Room internals, how we collected the methods for instrumentation, and what tools are available out there for dealing with ASM.&lt;/p&gt;

&lt;p&gt;The code is available in the &lt;a href="https://github.com/getsentry/sentry-android-gradle-plugin"&gt;sentry-android-grade-plugin&lt;/a&gt; repo, specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/getsentry/sentry-android-gradle-plugin/blob/37eff955853fd395c0c6bf4c633518ce35dd508c/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryPlugin.kt#L62-L86"&gt;Registering a ClassVisitorFactory&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/getsentry/sentry-android-gradle-plugin/blob/37eff955853fd395c0c6bf4c633518ce35dd508c/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SpanAddingClassVisitorFactory.kt"&gt;ClassVisitorFactory&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the way, if you are already using the &lt;a href="https://docs.sentry.io/platforms/android/proguard/#gradle-configuration"&gt;Sentry Android Gradle plugin&lt;/a&gt;, give this new Room instrumentation a try in version &lt;code&gt;3.0.0-beta.1&lt;/code&gt; , we would appreciate your feedback via &lt;a href="https://github.com/getsentry/sentry-android-gradle-plugin/issues"&gt;GitHub issues&lt;/a&gt;. If not, it’s time to start using Sentry — &lt;a href="https://sentry.io/demo"&gt;request a demo&lt;/a&gt; and &lt;a href="https://sentry.io/signup/"&gt;try it out for free&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>android</category>
      <category>java</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Development Environment Observability with Sentry</title>
      <dc:creator>Rahul Chhabria</dc:creator>
      <pubDate>Fri, 19 Nov 2021 04:23:46 +0000</pubDate>
      <link>https://dev.to/sentry/development-environment-observability-with-sentry-4h1a</link>
      <guid>https://dev.to/sentry/development-environment-observability-with-sentry-4h1a</guid>
      <description>&lt;p&gt;By: &lt;a href="https://blog.sentry.io/authors/armen-zambrano"&gt;Armen Zambrano G.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At Sentry, we’re always looking for innovative ways to dogfood our product. Over the last year we added Sentry’s error monitoring to our developer environment so that we could better understand the health of it.&lt;/p&gt;

&lt;p&gt;In this blog post I’m going to touch on how fragile local development environments can be, how we brought observability into what’s happening by introducing Sentry, and what outcomes it has driven for our engineering organization.&lt;/p&gt;

&lt;h1&gt;
  
  
  Development environments are fragile
&lt;/h1&gt;

&lt;p&gt;As programmers, we typically spend a lot of time getting our local development environment set up: installing dependencies, managing multiple versions of an SDK or supporting tools, and so on.&lt;/p&gt;

&lt;p&gt;Getting set up can take some time, however it’s usually straightforward because you’re starting from a clean slate. What becomes more challenging is when a previously good environment gets into a bad state. For example, you need a newer Python version for your virtualenv, a new library is missing, or your pre-commit hooks are out of date.&lt;/p&gt;

&lt;p&gt;When things go wrong, it’s frustrating because your real work – building software – stops and you instead have to invest your time trying to unstick your environment. When the answer isn’t obvious, you have to go into Slack, ask for help, and hope that someone is around who can help you troubleshoot. And now you’ve interrupted their regular work too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pnblpNKm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9o037pato5fusi5mqowd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pnblpNKm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9o037pato5fusi5mqowd.png" alt="Image description" width="880" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Getting alerted to problems during development
&lt;/h1&gt;

&lt;p&gt;Typically, Sentry is used for monitoring software in production. When your software goes live in front of users, you want visibility to understand whether that software is operating successfully. Are there any errors? Is the performance acceptable?&lt;/p&gt;

&lt;p&gt;However, for our Developer Productivity team, the local development environment used by our engineers is our production environment. Software engineers at Sentry are our users, and we similarly want to understand if the development tools we’re shipping are working successfully. For instance, are Python packages failing to install for some users?&lt;/p&gt;

&lt;p&gt;What if we could be alerted when a developer’s environment goes bad? To figure this out, we instrumented Sentry into our local development toolchain.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fL9LfHNY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wjqlist3cq3xxdly38ho.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fL9LfHNY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wjqlist3cq3xxdly38ho.png" alt="Image description" width="880" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;When we don’t properly triage development environment issues, developers can suffer the pain for some time until they report it.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Instrumenting Bash scripts with Sentry
&lt;/h1&gt;

&lt;p&gt;Our development environment is generally powered by a lot of Bash scripts (If you have other environments go to &lt;a href="https://docs.sentry.io/platforms/"&gt;the platforms page&lt;/a&gt; and see if yours is supported). Those scripts do everything from &lt;a href="https://github.com/getsentry/sentry/blob/6fd2793e9131c8062dc70be6a0ade8346280ef03/scripts/pyenv_setup.sh#L82-L98"&gt;installing Python versions&lt;/a&gt;, &lt;a href="https://github.com/getsentry/sentry/blob/6fd2793e9131c8062dc70be6a0ade8346280ef03/scripts/lib.sh#L134-L146"&gt;setting up Git commit hooks&lt;/a&gt;, &lt;a href="https://github.com/getsentry/sentry/blob/6fd2793e9131c8062dc70be6a0ade8346280ef03/config/hooks/post-merge#L20-L26"&gt;prompting developers to update their dependencies&lt;/a&gt;, and so on.&lt;/p&gt;

&lt;p&gt;To have visibility into what was happening in our Bash scripts, we turned to &lt;a href="https://docs.sentry.io/product/cli/send-event/"&gt;sentry-cli&lt;/a&gt;. Sentry-cli is a command line executable written in Rust that communicates with the Sentry API. Typically it’s used for administrative tasks like sending a test event from the shell, uploading source maps, or letting Sentry know about a new version of your software.&lt;/p&gt;

&lt;p&gt;Sentry-cli also has an interesting feature: it provides a bash hook that detects unhandled failures in a bash script and automatically reports those failures to Sentry. In the example below, sentry-cli is initialized in a Bash script (see &lt;a href="https://docs.sentry.io/product/cli/send-event/#bash-hook"&gt;docs&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qLg1Wh89--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7nou55q6lhhsus52vymm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qLg1Wh89--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7nou55q6lhhsus52vymm.png" alt="Image description" width="880" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the Bash script fails, an error event is sent to Sentry with a traceback, the values of any active environment variables, and the console output is captured as breadcrumbs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iZwH0BHH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cqwvesu22jhhippived3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iZwH0BHH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cqwvesu22jhhippived3.png" alt="Image description" width="880" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Bash traceback showing the lines and scripts that were involved when the error occurred.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EGwReYWY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xooh0jj7euc5chpasixd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EGwReYWY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xooh0jj7euc5chpasixd.png" alt="Image description" width="880" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Sentry’s breadcrumb trail shows the emitted debug output that occurred before the failed Bash command&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Monitoring our development environment users
&lt;/h1&gt;

&lt;p&gt;Let’s fast forward: at this point we instrumented most of our development environment scripts with Sentry. Let’s look at what the flow for helping a co-worker bootstrapping their development environment is like.&lt;/p&gt;

&lt;p&gt;As mentioned earlier, if a script failure is encountered, sentry-cli sends an error event to Sentry’s servers. This has the effect of creating an alert within Sentry, which gets broadcasted to our team channel on Slack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_wRNawNP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xmt618dyhdcq63hu6ndm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_wRNawNP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xmt618dyhdcq63hu6ndm.png" alt="Image description" width="880" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From there, we click through the Slack link which takes us to the event inside Sentry. Then we can determine which engineer is facing the issue (this is taken from the $USER environment variable) as well as what the issue is from the console output.&lt;/p&gt;

&lt;p&gt;Since we know who’s facing the issue and what kind of problem they’re facing, we can get in touch with the developer armed with some context and a readiness to help!&lt;/p&gt;

&lt;p&gt;We can easily find the root cause of an issue that may be difficult to reproduce because we reached out to the engineer near the moment of occurrence and are invested in getting it fixed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---v1SiDdK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hd3phstz6c7ugk8xd6qp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---v1SiDdK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hd3phstz6c7ugk8xd6qp.png" alt="Image description" width="880" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Here’s an example of an issue we had not been able to pinpoint for several months. Working with the engineer while they were investigating it lead to a fix.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  The impact
&lt;/h1&gt;

&lt;p&gt;Instrumenting our development environment with Sentry has allowed us to monitor the health of our development environment and respond more quickly when someone faces an issue. Over the last year, we’ve seen the following positive outcomes for the engineering organization:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;We can help an engineer that is currently investigating an ongoing issue. This increases the chances of fixing the root cause of the issue and preventing others from facing the same problem rather than the issue being worked around (Not all engineers are comfortable solving their own dev env problems).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We can be notified of tooling regressions. It’s easier to fix a regression when you’re notified soon after having merged your code. Without alerting, a regression might take a while to be reported. For instance, a developer may work around the issue on multiple occasions a few weeks after the code at fault has landed.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IUNNplLc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c1mdistu5ddtnkh8dsqk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IUNNplLc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c1mdistu5ddtnkh8dsqk.png" alt="Image description" width="880" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This shows the development environment issues in the last three months and shows when regressions were introduced&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Wrapping up
&lt;/h1&gt;

&lt;p&gt;We have covered how instrumenting a development environment with Sentry can positively impact the productivity of your engineering organization by increasing the observability and decreasing the response time to issues.&lt;/p&gt;

&lt;p&gt;Thanks for reading this post. If you want to try out Sentry without creating an account, visit &lt;a href="https://sentry.io/demo/sandbox/"&gt;the Sentry Sandbox&lt;/a&gt; and feel free to play with it!&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href="https://github.com/billyvg"&gt;Billy Vong&lt;/a&gt; for paving the way with the bootstrapping code, &lt;a href="https://benv.ca/"&gt;Ben Vinegar&lt;/a&gt; for encouraging me to write this post, and for &lt;a href="https://github.com/logicalbomb"&gt;Zac Propersi’s&lt;/a&gt; managerial support.&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>productivity</category>
      <category>bash</category>
      <category>python</category>
    </item>
    <item>
      <title>Keep Gamers Gaming — Application Monitoring for Unity</title>
      <dc:creator>Rahul Chhabria</dc:creator>
      <pubDate>Thu, 28 Oct 2021 16:48:42 +0000</pubDate>
      <link>https://dev.to/sentry/keep-gamers-gaming-application-monitoring-for-unity-21pn</link>
      <guid>https://dev.to/sentry/keep-gamers-gaming-application-monitoring-for-unity-21pn</guid>
      <description>&lt;p&gt;By: &lt;a href="https://blog.sentry.io/authors/rahul-chhabria" rel="noopener noreferrer"&gt;Rahul Chhabria&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Given the millions of registered Unity developers worldwide, Unity is arguably the most popular engine used to develop games. But, whether you’re building the latest FPS or a turn-based classic, you need visibility in how your game is performing on a gamer’s device.&lt;/p&gt;

&lt;p&gt;More than 800 game development and platform companies rely on Sentry, from &lt;a href="https://sentry.io/customers/outfit7/" rel="noopener noreferrer"&gt;OutFit7&lt;/a&gt; to &lt;a href="https://sentry.io/customers/riot/" rel="noopener noreferrer"&gt;Riot&lt;/a&gt;, Epic Games, and Unity. With Sentry, Unity and the rest of these organizations see the events (both errors and performance issues now) that actually matter, solve code issues quickly, and learn how each release performs over time.&lt;/p&gt;

&lt;p&gt;To help Unity developers monitor their code across platforms and devices, we updated &lt;a href="http://sentry.io/for/unity" rel="noopener noreferrer"&gt;Sentry for Unity&lt;/a&gt;. Now you don’t have to figure out what device, the OS, seeing slow transactions, or deal with app version fragmentation when something goes wrong. This means that while your players are trying to catch them all, Sentry is catching all your errors and performance issues, and telling you how to fix them before they affect the player experience.&lt;/p&gt;

&lt;h1&gt;
  
  
  Performance Monitoring
&lt;/h1&gt;

&lt;p&gt;Now, errors are only half the story. Unity developers also need to see if they’re providing a snappy and responsive gaming experience. That visibility means being able to know that your game is loading objects in a given viewport quickly and responding to user input as intended without any delay. Without this data, untreated performance problems can become serious errors. &lt;a href="https://sentry.io/for/performance" rel="noopener noreferrer"&gt;Performance&lt;/a&gt;, our aptly named performance monitoring solution, quickly highlights what occurred for a specific error or issue, the conditions that caused the bottleneck or latency issue, and the endpoints or operations that consume the most time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd8npj7jol394ol4guc81.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd8npj7jol394ol4guc81.png" alt="Image description" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re feeling adventurous—and based on how many of you adopted early-access Sentry for Unity (&amp;gt; 500 teams, but who’s counting?), many of you are—you can now try Performance Monitoring for Unity in Early Access. Check out the &lt;a href="https://docs.sentry.io/platforms/unity/performance/" rel="noopener noreferrer"&gt;docs&lt;/a&gt; or the &lt;a href="https://sentry.io/orgredirect/organizations/:orgslug/performance/" rel="noopener noreferrer"&gt;performance tab in product&lt;/a&gt; to configure tracing.&lt;/p&gt;

&lt;h1&gt;
  
  
  Native Crash Reporting
&lt;/h1&gt;

&lt;p&gt;For you iOS and Android game developers, there are some monitoring services that can report back when your game crashes—but events are usually just listed as an “abnormal session.” With this vague information, you’ll probably end up playing an extra fun round of 21 questions with your code and/or customer support. Alternatively, if you prefer to skip that guesswork, Sentry can tell you if a crash happens in the game or the native layer, like a plugin or native library you could be using.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2d5plia9kn6wq20y2u2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2d5plia9kn6wq20y2u2.png" alt="Image description" width="800" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Along with native iOS and Android crash reporting, you’ll have access to session data in real-time and know how a release impacts your users with metrics like crash-free sessions, crash-free users, and the overall health of a release. New Release management features like &lt;a href="https://docs.sentry.io/product/releases/usage/sorting-filtering/#filtering-releases" rel="noopener noreferrer"&gt;sorting &amp;amp; filtering&lt;/a&gt; releases and &lt;a href="https://docs.sentry.io/product/releases/health/#adoption-stages" rel="noopener noreferrer"&gt;Adoption Stages&lt;/a&gt; give game developers all of the data to decide if the team is ready to push a new release. Did that bug-fix release work? Or are the new characters you added in the latest rev functioning and playable? You’ll know the answer to these questions and more, immediately.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4gs4u98knflgzjllxa0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4gs4u98knflgzjllxa0.png" alt="Image description" width="800" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not everyone is playing your games at the same time, meaning traffic will likely vary and constantly fluctuate as their gameplay changes and increases. So with Sentry, developers can define &lt;a href="https://docs.sentry.io/product/alerts/create-alerts/issue-alert-config/#when-conditions-triggers" rel="noopener noreferrer"&gt;percent-based alerts&lt;/a&gt; which adjust to changes in traffic, making sure you get notified of and effectively prioritize the most critical gamer experience issues.&lt;/p&gt;

&lt;h1&gt;
  
  
  Get Started
&lt;/h1&gt;

&lt;p&gt;Install the Unity SDK via the &lt;a href="https://docs.unity3d.com/Manual/upm-ui-giturl.html" rel="noopener noreferrer"&gt;Unity Package Manager using a Git URL&lt;/a&gt; pointing to &lt;a href="https://docs.sentry.io/platforms/unity/#install" rel="noopener noreferrer"&gt;Sentry’s SDK&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpj2a6q6dbw2hzio7ggza.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpj2a6q6dbw2hzio7ggza.png" alt="Image description" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unity developers can benefit from the rich context that Sentry provides by simply creating a Sentry project and pasting their DSN in Unity.&lt;/p&gt;

&lt;p&gt;In the event players hit an unexpected issue—anything from your game failing to instantiate specific objects or simply corrupted &lt;code&gt;savegames&lt;/code&gt;—Sentry will report on device type, orientation, battery level (if applicable), GPU state, CPU info, and everything about the software and OS that your game is running on. You may ask, “But what if someone is playing my game offline?” No problem—both with offline gaming and intermittent network connectivity, Sentry will phone home with these events after the connection restores.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdqh2x9srwgrg68ah66my.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdqh2x9srwgrg68ah66my.png" alt="Image description" width="800" height="566"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If one of the best parts of your day is testing your game on nine different platforms to find where your gamers are having sub-optimal experiences—Sentry may, unfortunately, cut it short. But hey, if you still want to debug, that’s fine. Your secret is safe with me.&lt;/p&gt;

&lt;p&gt;The number of devices your games run on is growing exponentially. Don’t collect a bunch of random devices and consoles just to recreate a single issue. Instead, use Sentry which automatically reports errors and performance issues as they happen, the platform on which they occur, and all the contexted needed to identify the root cause. Unless your player is complaing of a blurry screen when playing Rad Racer. We don’t have a boolean for &lt;code&gt;isWearing3DGlasses&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>unity3d</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>We Gave $154,999.89 to Open Source Maintainers</title>
      <dc:creator>Rahul Chhabria</dc:creator>
      <pubDate>Tue, 26 Oct 2021 21:37:31 +0000</pubDate>
      <link>https://dev.to/sentry/we-gave-15499989-to-open-source-maintainers-3m08</link>
      <guid>https://dev.to/sentry/we-gave-15499989-to-open-source-maintainers-3m08</guid>
      <description>&lt;p&gt;By: &lt;a href="https://blog.sentry.io/authors/chad-whitacre"&gt;Chad Whitacre&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sentry is an open source company. We &lt;a href="https://github.com/getsentry/sentry/commit/3c2e87573d3bd16f61cf08fece0638cc47a4fc22"&gt;started out in 2008&lt;/a&gt; as a small open source side project, and we grew within the community for years before commercializing in 2012. We’ve worked hard to keep our full product as open source as possible, while scaling as a business. Considering our commitment to open source, we are grateful to be able to give back to the community (and what better time than &lt;a href="https://hacktoberfest.digitalocean.com/giving"&gt;during Hacktoberfest&lt;/a&gt;, amirite?).&lt;/p&gt;

&lt;p&gt;(P.S. The good folks at &lt;a href="https://sustainoss.org/"&gt;Sustain&lt;/a&gt; brought us onto their &lt;a href="https://podcast.sustainoss.org/"&gt;podcast&lt;/a&gt; for a deep dive on this funding initiative. If you like what you’re reading here, mosey on over and &lt;a href="https://podcast.sustainoss.org/96"&gt;check out the episode&lt;/a&gt;.)&lt;/p&gt;

&lt;h1&gt;
  
  
  Background
&lt;/h1&gt;

&lt;p&gt;Sentry has a long history of sharing our business success with the open source community. We have supported the Python and Django Software Foundations since 2016 with donations totalling about $75,000. Over the years, we’ve also supported smaller projects like Vue.js, Django Rest Framework, and Psycopg. In 2019, we &lt;a href="https://blog.sentry.io/2019/10/03/scaling-techqueria-with-sentrys-open-source-grant"&gt;awarded a $10,000 grant&lt;/a&gt; to Techqueria, a nonprofit serving the largest community of Latinx in tech, for open source work on their website. And in 2020, the Sentry team was pleasantly surprised to receive a $10,000 grant from Indeed, which we &lt;a href="https://blog.sentry.io/2020/02/18/funding-open-source"&gt;matched and regifted to five projects&lt;/a&gt; in the Python and Rust ecosystems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TJDsDkb7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2plb5m8xmup6pfxgdf67.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TJDsDkb7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2plb5m8xmup6pfxgdf67.png" alt="Image description" width="752" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Outside of direct financial contributions, Sentry invests in the open source community in many ways: we &lt;a href="https://sentry.io/for/open-source/"&gt;donate SaaS credits to open source projects&lt;/a&gt;, sponsor conferences and meetups, and contribute patches to upstream projects. Additionally, Sentry has a general employee philanthropic donation-matching program in which open source foundations are eligible.&lt;/p&gt;

&lt;h1&gt;
  
  
  Sentry’s FOSS Fund 155
&lt;/h1&gt;

&lt;p&gt;Indeed’s generous $10,000 donation to us was part of an industry initiative they launched called &lt;a href="https://engineering.indeedblog.com/blog/2019/11/foss-fund-adopters/"&gt;FOSS Fund Adopters&lt;/a&gt;; &lt;a href="https://github.com/microsoft/foss-fund"&gt;Microsoft&lt;/a&gt; and &lt;a href="https://engineering.salesforce.com/search?q=foss%20fund"&gt;Salesforce&lt;/a&gt; are also on board. When we announced our matching regift, we also committed to &lt;a href="https://blog.sentry.io/2020/02/18/funding-open-source"&gt;increasing our own giving to open source&lt;/a&gt;, which we have now done with our own iteration that we’re calling FOSS Fund 155. We distributed $154,999.89 to 108 recipients, grouped under three line items:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Foundation memberships (52%),&lt;/li&gt;
&lt;li&gt;Long-tail projects through &lt;a href="https://github.com/sponsors"&gt;GitHub Sponsors&lt;/a&gt; and &lt;a href="https://opencollective.com/"&gt;Open Collective&lt;/a&gt; (36%), and&lt;/li&gt;
&lt;li&gt;Internships for new contributors through &lt;a href="https://www.outreachy.org/"&gt;Outreachy&lt;/a&gt; (13%).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Why that suspiciously specific amount? Well, &lt;a href="https://gratipay.news/your-company-should-probably-pay-2000-per-person-for-open-source-9205443e209d"&gt;napkin math suggests&lt;/a&gt; that tech companies receive approximately $2,000 of value from volunteer labor in the open source community, per engineer on staff. Sentry employs approximately 75 engineers, and 75 × $2,000 = $150,000, so that became our target budget. We bumped it up a bit to meet foundation membership fee thresholds. As to the 11¢ offset, we have currency conversion to thank.&lt;/p&gt;

&lt;p&gt;One of our goals for FOSS Fund 155 is to &lt;strong&gt;contribute meaningfully&lt;/strong&gt;. We believe that contributing $2,000 per engineer is a meaningful amount that fairly compensates the value we receive from open source volunteers.&lt;/p&gt;

&lt;p&gt;Another of our goals is to &lt;strong&gt;lead by example&lt;/strong&gt; within the industry. We want to raise the bar for how companies in the tech industry think about their financial relationships with the open source community. Transparency into companies’ open source contributions is valuable to the community and the industry, and to individuals making career decisions. Reporting this information per individual engineering contributor makes it easier to compare across companies.&lt;/p&gt;

&lt;h1&gt;
  
  
  Allocating Our Budget
&lt;/h1&gt;

&lt;p&gt;All tech companies &lt;a href="https://xkcd.com/2347/"&gt;stand on the shoulders of community-supported open source giants&lt;/a&gt;, and Sentry is no exception. With this fund we prioritized support for our dependencies in order to strengthen our supply chain. But, more than that—Sentry itself was a volunteer-run project for many years. Yes, we took a commercial route, but we respect the many projects that have chosen a different path. Maintainers should be able to determine their own future, and financially supporting our community-managed dependencies makes that a bit more feasible for them.&lt;/p&gt;

&lt;p&gt;By auditing our product architecture, we generated a list of seven major community-led open source projects that are foundational to our success: Python, Django, Rust, JavaScript, PostgreSQL, Apache, and Linux. These projects are all backed by formal non-profit foundations; we added an eighth foundation, the Open Source Initiative, to represent the open source community as a whole. We decided to allocate half of our budget (52%) to these eight foundations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W5eRnk9c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/395f1f9riul6zzzsxvcy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W5eRnk9c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/395f1f9riul6zzzsxvcy.png" alt="Image description" width="752" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another way we think about our goal to &lt;strong&gt;contribute meaningfully&lt;/strong&gt; is in intentionally welcoming new contributors into the open source community. We ran a &lt;a href="https://blog.sentry.io/2019/10/03/scaling-techqueria-with-sentrys-open-source-grant"&gt;diversity-focused open source grant program&lt;/a&gt; in-house in 2019, and it was a great experience. However, we think we can make even more of an impact by partnering with an existing organization. &lt;a href="https://www.outreachy.org/"&gt;Outreachy&lt;/a&gt; has &lt;a href="https://www.outreachy.org/alums/"&gt;a great track record&lt;/a&gt; of providing internships in open source to individuals from underrepresented backgrounds, so we allocated $19,500 (13%) to sponsor three interns to work on projects in our dependencies. Debian and LLVM were able to use the funds directly. Git asked us to redirect their portion to the Outreachy general fund.&lt;/p&gt;

&lt;p&gt;Another of our goals for this funding program is to &lt;strong&gt;reinforce open source as part of Sentry’s company culture&lt;/strong&gt;, so we involved our employees in allocating the remainder (36%) of our budget. We did some &lt;a href="https://rawcdn.githack.com/getsentry/deps/fy2022/fundable.html"&gt;dependency analysis&lt;/a&gt; using the GitHub API to identify the community-led, fundable projects within the ecosystems we inhabit. We asked our employees to influence our contributions to these projects by voting on them, and also to nominate other projects that are meaningful to them. We ended up with a list of 97 projects to support, in addition to the eight foundations and the three internships.&lt;/p&gt;

&lt;p&gt;Throughout this exercise, we filtered out open source projects that have strong corporate leadership (Sentry itself would be an example), as well as our employees’ side projects (since we already compensate them financially). This serves our goal to &lt;strong&gt;contribute meaningfully&lt;/strong&gt; by directing our money where it can have the most impact. We also tried to balance breadth and depth, focusing larger amounts on some projects, while giving at least a little bit to every maintainer we identified.&lt;/p&gt;

&lt;p&gt;In the end, we distributed $154,999.89 to 108 recipients: eight foundations, 97 individual projects, and three internship partners. Visit our &lt;a href="https://github.com/orgs/getsentry/sponsoring"&gt;GitHub Sponsors&lt;/a&gt; (GHS) and &lt;a href="https://opencollective.com/sentry"&gt;Open Collective&lt;/a&gt; (OC) profiles for the details; below is a summary (sorry it’s cut off on mobile).&lt;/p&gt;

&lt;h1&gt;
  
  
  Where the $154,999.89 Went
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TcO4Pn3m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hljws4fl8kkrkhut702d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TcO4Pn3m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hljws4fl8kkrkhut702d.png" alt="Image description" width="880" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  How the Money Got There
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DMHH3PZk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6xaluz1vbkqys52e8tf6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DMHH3PZk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6xaluz1vbkqys52e8tf6.png" alt="Image description" width="880" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Looking Ahead
&lt;/h1&gt;

&lt;p&gt;Our final goal for Sentry’s open source funding program is to &lt;strong&gt;sustain in a sustainable way&lt;/strong&gt;. We feel good about what we were able to put together this year for Sentry’s FOSS Fund 155, and we will continue to iterate on our open source financial support as we continue to scale. &lt;strong&gt;Thank you&lt;/strong&gt; to all of the maintainers out there—especially the volunteers. We appreciate you, and we look forward to financially supporting your work for many years.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Performance Monitoring in GraphQL</title>
      <dc:creator>Rahul Chhabria</dc:creator>
      <pubDate>Tue, 31 Aug 2021 22:51:59 +0000</pubDate>
      <link>https://dev.to/sentry/performance-monitoring-in-graphql-ogk</link>
      <guid>https://dev.to/sentry/performance-monitoring-in-graphql-ogk</guid>
      <description>&lt;p&gt;By: Enrique Fueyo&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://www.linkedin.com/in/efueyo/" rel="noopener noreferrer"&gt;Enrique Fueyo Ramírez&lt;/a&gt; is the Co-founder and CTO of &lt;a href="//www.lang.ai"&gt;Lang.ai&lt;/a&gt;. Here’s how he and his team at Lang.ai instrumented performance monitoring for GraphQL resolvers.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Tech Stack
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;At Lang.ai we use &lt;a href="//www.sentry.io"&gt;Sentry&lt;/a&gt; in our systems for Error Monitoring and recently added Performance Monitoring&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lang.ai’s User Interface is a React App using a &lt;a href="https://graphql.org/" rel="noopener noreferrer"&gt;GraphQL&lt;/a&gt; API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We use &lt;a href="https://www.apollographql.com/" rel="noopener noreferrer"&gt;Apollo&lt;/a&gt; to connect the frontend and the GraphQL backend.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Problem
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Some months ago we started experiencing some degradation in a few graphql queries. The queries were complex (big) queries and it was tricky to debug which part of the request was slowing down the response.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Goal
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We wanted to monitor the performance of each &lt;a href="https://www.apollographql.com/docs/apollo-server/data/resolvers/" rel="noopener noreferrer"&gt;Graphql Resolver&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ideally, we wanted to monitor the performance of every resolver without explicitly adding it (we didn’t want to proactively add a start and stop lines of code all around our functions’ bodies).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Solution
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Create a Sentry transaction for each graphql request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each Sentry transaction is initialized with its own &lt;a href="https://docs.sentry.io/platforms/javascript/enriching-events/context/" rel="noopener noreferrer"&gt;context&lt;/a&gt;. &lt;a href="https://docs.sentry.io/platforms/javascript/performance/instrumentation/custom-instrumentation/" rel="noopener noreferrer"&gt;Create a Transaction&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Transaction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@sentry/types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... other context fields for your context&lt;/span&gt;
  &lt;span class="nl"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Transaction&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createContext&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... create other context fields&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Sentry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startTransaction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gql&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GraphQLTransaction&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// this will be the default name, unless the gql query has a name&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Add a span for each resolver&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To intercept the life-cycle of each resolver and create and finish a span, we needed to create an Apollo Plugin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ApolloServerPlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;apollo-server-plugin-base&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApolloServerPlugin&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;requestDidStart&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;operationName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// set the transaction Name if we have named queries&lt;/span&gt;
      &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;operationName&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;willSendResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// hook for transaction finished&lt;/span&gt;
        &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nf"&gt;executionDidStart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;willResolveField&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// hook for each new resolver&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;span&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startChild&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
              &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resolver&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// this will execute once the resolver is finished&lt;/span&gt;
              &lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;And then we have to connect all the pieces on server initialization
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ApolloServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;apollo-server-micro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;SentryPlugin&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./sentry-plugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apolloServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApolloServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// ... your ApolloServer options&lt;/span&gt;
  &lt;span class="c1"&gt;// Create context function&lt;/span&gt;
  &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;createContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="c1"&gt;// Add our sentry plugin&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SentryPlugin&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once your server starts receiving requests it will send every transaction info to your configured Sentry account. You should see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgz88m71zeln440276xox.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgz88m71zeln440276xox.png" alt="successful setup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And you can also see the detail of each individual transaction with its resolvers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5qq0ee16r8zlozeoqy1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5qq0ee16r8zlozeoqy1.png" alt="transaction detail"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  One last consideration
&lt;/h1&gt;

&lt;p&gt;We could have created the transaction directly in the plugin, inside the &lt;code&gt;requestDidStart&lt;/code&gt; function and omit any references to the &lt;code&gt;Context&lt;/code&gt;. But, if we make the &lt;code&gt;transaction&lt;/code&gt; accessible from the &lt;code&gt;Context&lt;/code&gt;, each resolver can access it and we can create more &lt;code&gt;spans&lt;/code&gt; inside the resolvers for more fine grained information.&lt;/p&gt;

&lt;p&gt;Accessing the transaction from the resolver should also be helpful for Sentry’s &lt;a href="https://docs.sentry.io/product/performance/distributed-tracing/" rel="noopener noreferrer"&gt;Distributed Tracing&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>graphql</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Four Metrics Every Mobile Developer Should Care About</title>
      <dc:creator>Rahul Chhabria</dc:creator>
      <pubDate>Tue, 24 Aug 2021 01:05:29 +0000</pubDate>
      <link>https://dev.to/sentry/four-metrics-every-mobile-developer-should-care-about-228h</link>
      <guid>https://dev.to/sentry/four-metrics-every-mobile-developer-should-care-about-228h</guid>
      <description>&lt;p&gt;By: &lt;a href="https://blog.sentry.io/authors/philipp-hofmann" rel="noopener noreferrer"&gt;Philipp Hofmann&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Slow apps frustrate users, which leads to bad reviews, or customers that swipe left to competition. Unfortunately, seeing and solving performance issues can be a struggle and time-consuming.&lt;/p&gt;

&lt;p&gt;Most developers use profilers within IDEs like Android Studio or Xcode to hunt for bottlenecks and automated performance tests to catch performance regressions in their code during development. However, testing an application before it ships is not enough.&lt;/p&gt;

&lt;p&gt;To catch the most frustrating performance issues, you need to explore what’s happening on your users’ devices. That means visibility into how fast your app starts, duration of HTTP requests, number of slow and frozen frames, how fast your views are loading, and more. With Sentry for Mobile, you can now easily monitor your iOS and Android app’s performance in real-time without additional setup (or accumulating a pile of testing devices).&lt;/p&gt;

&lt;h1&gt;
  
  
  Mobile Vitals
&lt;/h1&gt;

&lt;p&gt;We believe there are four metrics every mobile team should track to better understand how their app is performing: Cold starts, warm starts, slow frames, and frozen frames. These four metrics, as a core part of Sentry’s performance monitoring, gives you the details you need to not only prioritize critical performance issues but trace the issue down to the root cause to solve them faster.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F98e8pxee7zba40ehc2vj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F98e8pxee7zba40ehc2vj.png" alt="mobile-vitals"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Measuring App Start
&lt;/h1&gt;

&lt;p&gt;When a user taps on your app icon, it should start fast. On iOS, Apple recommends your app take at most 400ms to render the first frame. On Android, the &lt;a href="https://developer.android.com/topic/performance/vitals/launch-time#av" rel="noopener noreferrer"&gt;Google Play console&lt;/a&gt; warns you when a cold start takes longer than 5 seconds or a warm start longer than 2 seconds.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cold start: App launched for the first time after a reboot or update.&lt;/li&gt;
&lt;li&gt;Warm start: App launched at least once and is partially in memory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exact definitions differ slightly per platform. For more details, please check out our &lt;a href="https://docs.sentry.io/platforms/apple/guides/ios/performance/instrumentation/automatic-instrumentation/" rel="noopener noreferrer"&gt;iOS&lt;/a&gt; and &lt;a href="https://docs.sentry.io/platforms/android/performance/instrumentation/automatic-instrumentation/" rel="noopener noreferrer"&gt;Android&lt;/a&gt; docs.&lt;/p&gt;

&lt;p&gt;No matter the platform, it is crucial that your app starts quickly to provide a delightful user experience. That’s why on iOS, Mac Catalyst, tvOS, and Android we track how long your app needs to draw your first frame. We grab this information and add spans for different phases of the app start. Here is an example from iOS:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghxfsnt1sw8z0c7rv9ox.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghxfsnt1sw8z0c7rv9ox.png" alt="measuring-app-start"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On Android, it is trickier to hook into the initialization phases of the app start, and therefore we currently add one span from the application launch to the first auto-generated UI transaction. Still, this information is very useful and can help you to improve the duration of your app start.&lt;/p&gt;

&lt;h1&gt;
  
  
  Slow and Frozen Frames
&lt;/h1&gt;

&lt;p&gt;Unresponsive UI, animation hitches, or just jank annoy users and degrade the user experience. Two measurements to track this unwanted experience are slow and frozen frames. A phone or tablet typically renders with 60 frames per second (fps).&lt;/p&gt;

&lt;p&gt;The frame rate can also be higher, especially as 120 fps displays are becoming more popular. With 60 fps, every frame has 16.67 ms to render. If your app needs more than 16.67 ms for a frame, it is a slow frame.&lt;/p&gt;

&lt;p&gt;Frozen frames are UI frames that take longer than 700 ms. An app that is running smoothly should not experience either. That’s why the SDK adds these two measurements to all transactions captured. The detail view of the transaction displays the slow, frozen, and total frames to the right.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flpcprs19g451ld2ndix9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flpcprs19g451ld2ndix9.png" alt="frozen-frames"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mobile Vitals are a core part of Sentry’s performance monitoring for mobile and unlocks more ways to spot bottlenecks and speed up your apps.&lt;/p&gt;

&lt;h1&gt;
  
  
  Mobile Performance Monitoring
&lt;/h1&gt;

&lt;p&gt;The purpose of Sentry’s performance monitoring is to track your application’s performance across multiple services. To measure Mobile Vitals, the SDKs capture distributed traces consisting of transactions and spans.&lt;/p&gt;

&lt;p&gt;Distributed tracing is a standard technology used for understanding what’s going on across distributed services, but it is still relatively new for mobile applications. A trace represents an operation you want to measure, like signing in or loading a view in your app. Both mentioned operations don’t only involve your app but also backend actions. Each trace consists of one or more transactions, which can contain one or more spans. For example, the trace of a login could then include a transaction of your app and two transactions of your backend services.&lt;/p&gt;

&lt;p&gt;Every transaction contains several spans representing a single unit of work, like reading a file or querying the database. The spans have a parent-child relationship, meaning a span can have multiple children and grandchildren. Here’s an example trace, broken down into transactions and spans:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7dme3mmru26xv4xh459.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7dme3mmru26xv4xh459.png" alt="tracing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to dig deeper into these concepts, check out &lt;a href="https://blog.sentry.io/2021/08/12/distributed-tracing-101-for-full-stack-developers/" rel="noopener noreferrer"&gt;Distributed Tracing 101 for Full Stack Developers&lt;/a&gt; by our very own, Ben Vinegar, VP of Engineering. For this blog post, let’s focus on transactions and take a look at an example creating a transaction with two child spans in Swift:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
    &lt;span class="kt"&gt;SentrySDK&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Load Messages"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"ui.load"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;getMessagesSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
    &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"GET /my/api/messages"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;getMessages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;getMessagesSpan&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;renderMessagesSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
    &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"ui"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Render Messages"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;renderMessages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;renderMessagesSpan&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// Finishes the transaction and sends it to Sentry&lt;/span&gt;
&lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running the code we can take a look at the transaction in Sentry:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj25f2n35z1glme22xrw2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj25f2n35z1glme22xrw2.png" alt="event-details"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Creating rich transactions manually would require writing a lot of code. That’s why we’ve made things easier with auto instrumentation. You can save yourself the headache of writing and maintaining code while still accessing the performance insights you need.&lt;/p&gt;

&lt;h1&gt;
  
  
  Automatic Instrumentation
&lt;/h1&gt;

&lt;p&gt;The automatic instrumentation lets you explore how long your views take to render, and HTTP requests need to finish. The SDK for Apple generates transactions for UIViewControllers on iOS, tvOS, and Mac Catalyst and creates spans for outgoing HTTP requests on all platforms. The SDK for Android captures transactions for Activities and Fragments and provides an integration for OkHttp. Sentry SDK for React Native can capture transactions automatically when using React Navigation router, and spans for both XHR and fetch requests. In the following months, we are working on adding support for Flutter.&lt;/p&gt;

&lt;p&gt;Here is an example of an auto-generated transaction on iOS for a &lt;code&gt;UIViewController&lt;/code&gt;. As we can see in the screenshot below, the SDK creates spans for each lifecycle method. In &lt;code&gt;viewWillAppear&lt;/code&gt;, we fire off an HTTP request for which a span is added. Our SDKs don’t automatically add spans for querying the database (yet), but I’m interested in how long my query runs. Therefore I have to add a span manually. We can achieve this by using &lt;code&gt;SentrySDK.span&lt;/code&gt; to access the current active span and call &lt;code&gt;SentrySDK.span.startChild&lt;/code&gt; to create the desired child span.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjto3totie2xsb5h8f5e4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjto3totie2xsb5h8f5e4.png" alt="ui-load"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the above transaction, I learn where my bottlenecks in the &lt;code&gt;UIViewController&lt;/code&gt; are. The &lt;code&gt;viewDidLoad&lt;/code&gt; method looks suspicious because it takes around 70 ms to complete, and I should investigate it. After looking at the code, I realize that I’m doing remarkable I/O on the main thread, which of course, is a no-go, and need to fix it immediately. Moreover, loading some entries from the database takes way longer than the HTTP request to finish, which also looks dubious and requires further investigation. After moving the I/O in &lt;code&gt;viewDidLoad&lt;/code&gt; to a background thread and adding an index to speed up my database query, the transaction looks way better now. Of course, the speed of the HTTP request will vary depending on the network conditions, but I cleared out the controllable bottlenecks, which speeds up my transaction from 250ms to around 20ms. This is a huge improvement.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhtm3yizmh6n0tn1hpw7a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhtm3yizmh6n0tn1hpw7a.png" alt="ui-load-improvement"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The duration of a transaction can vary greatly because of many circumstances. Stepping through individual transactions doesn’t give you a clear picture to understand if your app gets faster or slows down. Therefore, we provide graphs to explore how the duration of a transaction changes over time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feh696g11w0z7ovdun4lv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feh696g11w0z7ovdun4lv.png" alt="transaction-duration-over-time"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sentry also offers the possibility to explore slow, fastest, outlier, or recent transactions to find the misery or delight your users experience quickly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3ueuxj6mmdy2trrrogg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3ueuxj6mmdy2trrrogg.png" alt="slow-transactions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Along with many other possibilities to explore your transactions, Sentry links the related errors captured during a transaction at the bottom of the transaction summary page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mn46wjg4jogd0x7zjdy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mn46wjg4jogd0x7zjdy.png" alt="related-errors"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Auto-generated transactions combined with manual transactions unlocks rich data so you can see Mobile Vitals as well as other metrics such as &lt;a href="https://docs.sentry.io/product/performance/metrics/#user-misery" rel="noopener noreferrer"&gt;User Misery&lt;/a&gt; to give you a complete view into the performance of your application.&lt;/p&gt;

&lt;p&gt;To learn more, check out our docs for &lt;a href="https://docs.sentry.io/platforms/apple/performance/instrumentation/automatic-instrumentation/" rel="noopener noreferrer"&gt;Apple&lt;/a&gt;, &lt;a href="https://docs.sentry.io/platforms/android/performance/instrumentation/automatic-instrumentation/" rel="noopener noreferrer"&gt;Android&lt;/a&gt;, and &lt;a href="https://docs.sentry.io/platforms/react-native/performance/instrumentation/automatic-instrumentation/" rel="noopener noreferrer"&gt;React-Native&lt;/a&gt; and give automatic performance instrumentation on mobile a try. Watch out for beta releases of the &lt;a href="https://github.com/getsentry/sentry-dart" rel="noopener noreferrer"&gt;Flutter GitHub&lt;/a&gt; repo. We have plans to add these features to Flutter soon.&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>performance</category>
      <category>ios</category>
      <category>android</category>
    </item>
    <item>
      <title>Manage Releases with Sentry's Mobile App</title>
      <dc:creator>Rahul Chhabria</dc:creator>
      <pubDate>Tue, 03 Aug 2021 19:00:04 +0000</pubDate>
      <link>https://dev.to/sentry/manage-releases-with-sentry-s-mobile-app-2ba2</link>
      <guid>https://dev.to/sentry/manage-releases-with-sentry-s-mobile-app-2ba2</guid>
      <description>&lt;p&gt;By: &lt;a href="https://blog.sentry.io/authors/daniel-griesser"&gt;Daniel Griesser&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once a year we let our imagination go wild for a whole week during our annual Hackweek event. It’s where we come up with product updates, like &lt;a href="https://blog.sentry.io/2021/03/16/building-dark-mode/"&gt;dark mode&lt;/a&gt; support, design them and implement prototypes.&lt;/p&gt;

&lt;p&gt;The mobile engineering team came up with the idea for a Sentry mobile app that focuses on Release Health. We wanted to give developers a concise but comprehensive view of whether a release a release was healthy, errored, or experiencing abnormal crash sessions across multiple projects.&lt;/p&gt;

&lt;p&gt;Because we wanted to bring this to both &lt;a href="https://apps.apple.com/us/app/sentry-io/id1546709967"&gt;iOS&lt;/a&gt; and &lt;a href="https://play.google.com/store/apps/details?id=io.sentry.mobile.app"&gt;Android&lt;/a&gt;, and we only had a week to build a proof of concept we decided to build the app in Flutter. Oh by the way, we also have a &lt;a href="https://sentry.io/for/flutter/"&gt;Flutter SDK&lt;/a&gt;, it’s great.&lt;/p&gt;

&lt;h1&gt;
  
  
  Application Overview
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Sessions
&lt;/h2&gt;

&lt;p&gt;We fetch all projects from all your linked organizations that have session data. Every card represents one project and shows the name, organization, platform and a graph with the total session data over the last 24 hours. Below the cards the sessions are split up into healthy, errored, abnormal and crashed, all with individual graphs. There are also two cards showing crashfree sessions and users in percent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--F4xhzIDR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l2y3czoai1vx5fmuk4ly.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--F4xhzIDR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l2y3czoai1vx5fmuk4ly.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Settings
&lt;/h2&gt;

&lt;p&gt;In the settings screen users can view a list of all projects and favorite them. Those will show up first on the main screen. Apart from that, we show the usual suspects, like terms, sharing and logout, in the settings screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C7Gq1LiB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s9hcyguvllesrr947y8k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C7Gq1LiB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s9hcyguvllesrr947y8k.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Widgets
&lt;/h2&gt;

&lt;p&gt;Flutter takes the main concept of view rendering from react. The user interface is described using widgets which are derived from the applications state. When the state changes, the system creates the new widgets from it and only redraws the interface elements. You can head over to the &lt;a href="https://flutter.dev/docs/development/ui/widgets-intro"&gt;widgets intro documentation&lt;/a&gt; to learn more about this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redux
&lt;/h2&gt;

&lt;p&gt;We use Redux for state management. There is a central object holding the global application state. When an event in the application occurs, let’s say a reload of the session data, an action is dispatched.&lt;/p&gt;

&lt;p&gt;This action then goes through multiple middlewares. In this case, it would go through our API middleware, where it would trigger a network request. This request would then itself trigger new actions when it (asynchronously) finishes with success or a failure. All those actions travel through other middlewares as well and finally reach the reducer.&lt;/p&gt;

&lt;p&gt;The reducer is the only place where the global app state is mutated. In the sample above it would set a loading state when started and replace the loaded data upon success, or set an error state when there was a problem we want the user to know about.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pros
&lt;/h4&gt;

&lt;p&gt;• When state changes, widgets can be updated automatically. With the help of ViewModels implementing BLA, the Widget will only be rendered if the derived state changed. Neat!&lt;/p&gt;

&lt;p&gt;• With the reducer it,s explicit where state is mutated&lt;/p&gt;

&lt;h4&gt;
  
  
  Cons
&lt;/h4&gt;

&lt;p&gt;• The global state tends to get large quickly, even for such a small application. As the app will grow, we will have to think about how to best split this up.&lt;/p&gt;

&lt;p&gt;• Some Flutter API relies on Futures which does not yet play well with the Redux concept. One example of this is the “RefreshIndicator” widget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sentry SDK
&lt;/h2&gt;

&lt;p&gt;Of course we use our own Sentry SDK to check for issues. This was especially helpful during development, as we started out without sound null safety (more on that below) and were able to catch many issues with our SDK that would just show up in the console and could go easily unnoticed otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Drawing
&lt;/h2&gt;

&lt;p&gt;The prototype used a dependency with embedded &lt;a href="https://pub.dev/packages/flutter_echarts"&gt;ECharts&lt;/a&gt; for chart drawing. This made use of web views, so every chart we would draw would also embed a full blown web view into our widget hierarchy. We would quickly encounter performance issues as well as flickering UI when redrawing.&lt;/p&gt;

&lt;p&gt;This was a great way for us to get started during hackweek, as we use this JS dependency in the Sentry web application, but it was clear to us that we would need to replace this for the production app.&lt;/p&gt;

&lt;p&gt;We decided to draw the charts that our designers prepared for us in the Figma design ourselves with the help of the canvas API. Android developers will feel right at home with this API, as it is basically the same on Android. Flutter also uses Skia as the drawing library, so no surprise there.&lt;/p&gt;

&lt;p&gt;So in just a couple of lines of code we were able to draw the charts that our designers came up with, including gradients and cubic lines (which we removed again). Drawing was faster, pixel perfect and we could also get rid of a large dependency.&lt;/p&gt;

&lt;h1&gt;
  
  
  Tooling
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Android Studio
&lt;/h2&gt;

&lt;p&gt;We mainly used Android Studio with the Flutter plugin for application development. This is especially convenient for Android developers, as they can use a familiar environment when starting out with Flutter applications. Of course, you have the freedom to use &lt;a href="https://flutter.dev/docs/get-started/editor"&gt;whichever editor&lt;/a&gt; you are comfortable with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fastlane
&lt;/h2&gt;

&lt;p&gt;Fastlane is an open source tool written in Ruby. It is mostly used by the iOS community, but also works exceptionally well for Android. We use it locally to create release builds and ship them to the respective stores, as well as upload debug symbols to Sentry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static Analysis
&lt;/h2&gt;

&lt;p&gt;We used the &lt;a href="https://github.com/flutter/flutter/wiki/Using-the-Dart-analyzer"&gt;Dart analyzer&lt;/a&gt; during development. It helped us to keep track of code issues that might come up during development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sound Null Safety
&lt;/h2&gt;

&lt;p&gt;We migrated the application to &lt;a href="https://dart.dev/null-safety"&gt;sound null safety&lt;/a&gt; during development when we were already pretty far along with the app. Luckily, the Flutter team came up with an &lt;a href="https://dart.dev/null-safety/migration-guide"&gt;incremental way&lt;/a&gt; to do this. So we were able to convert file by file and have a working application during migration. I’m sure there are some Swift developers reading this who remember migrating in the early days. Yeah, my deepest condolences, I have been there.&lt;/p&gt;

&lt;h1&gt;
  
  
  That’s a Wrap
&lt;/h1&gt;

&lt;p&gt;Everything we do at Sentry is built in the open. Find us on &lt;a href="https://github.com/getsentry/sentry-mobile"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We have many ideas for future releases of the application, but ultimately it should be a tool that users find useful. So we can’t wait to hear your feedback, please give it a try!&lt;/p&gt;

&lt;p&gt;Download it now on the &lt;a href="https://apps.apple.com/us/app/sentry-io/id1546709967"&gt;Apple App Store&lt;/a&gt; and &lt;a href="https://play.google.com/store/apps/details?id=io.sentry.mobile.app"&gt;Google PlayStore&lt;/a&gt; and become a part of our Flutter journey.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Why we shipped a Next.js SDK, and how you can use it</title>
      <dc:creator>Rahul Chhabria</dc:creator>
      <pubDate>Thu, 10 Jun 2021 23:07:25 +0000</pubDate>
      <link>https://dev.to/sentry/why-we-shipped-a-next-js-sdk-and-how-you-can-use-it-14jp</link>
      <guid>https://dev.to/sentry/why-we-shipped-a-next-js-sdk-and-how-you-can-use-it-14jp</guid>
      <description>&lt;p&gt;As you could probably tell from the title, we shipped an &lt;a href="https://docs.sentry.io/platforms/javascript/guides/nextjs/" rel="noopener noreferrer"&gt;SDK for Next.js&lt;/a&gt;. This means you can capture errors, measure performance, manage releases, configure suspect commits, and automatically upload sourcemaps to view unminified JavaScript and TypeScript with zero(-ish) configuration.&lt;/p&gt;

&lt;p&gt;Why was Next.js next on our list? Well, it’s one of the fastest-growing &lt;a href="https://docs.sentry.io/platforms/javascript/guides/react/" rel="noopener noreferrer"&gt;React&lt;/a&gt; frameworks and developers love it. Next.js provides a developer experience that helps with building features rapidly, seeing local changes quickly, and reducing build times.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Next.js turns traditionally complex decisions into simple implementation details. Spinning up new pages with our existing React components and Material UI went from days to hours with Next."&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;– &lt;em&gt;Sean Parmelee, Applications Architect, Care.com&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To monitor Next.js projects in the past, you had to install our &lt;code&gt;@sentry/node&lt;/code&gt; and &lt;code&gt;@sentry/react&lt;/code&gt; SDKs — installing both and configuring your environment correctly was time-consuming and literally no fun. The new Next.js SDK does all that for you, and works swimmingly with our &lt;a href="https://www.npmjs.com/package/@sentry/webpack-plugin" rel="noopener noreferrer"&gt;Webpack Plugin&lt;/a&gt; or &lt;a href="https://docs.sentry.io/product/cli/" rel="noopener noreferrer"&gt;&lt;code&gt;sentry-cli&lt;/code&gt;&lt;/a&gt; to upload your source maps.&lt;/p&gt;

&lt;p&gt;To get started with Sentry for Next.js, simply install the SDK:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yarn add @sentry/nextjs&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And then configure:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx @sentry/wizard -i nextjs&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Configure &lt;a href="https://docs.sentry.io/platforms/javascript/guides/nextjs/performance/" rel="noopener noreferrer"&gt;performance monitoring&lt;/a&gt; by setting a &lt;code&gt;tracesSampleRate&lt;/code&gt; when you initialize the SDK, in both &lt;code&gt;sentry.client.config.js&lt;/code&gt; &amp;amp; &lt;code&gt;sentry.server.config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Sentry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;tracesSampleRate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you’ve done that, frontend transactions will be captured automatically. To capture API request transactions, wrap your handlers in our &lt;code&gt;withSentry&lt;/code&gt; helper. (Hint: If you’re already using our SDK to capture errors in those routes, you will have already done this. No extra configuration needed.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Sentry&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;sentry&lt;/span&gt;&lt;span class="sr"&gt;/nextjs’&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="nx"&gt;Sentry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Sentry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withSentry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can quickly track down poor-performing APIs or slow database queries. With new features like Quick Trace, &lt;a href="https://docs.sentry.io/product/sentry-basics/tracing/trace-view/" rel="noopener noreferrer"&gt;Trace View&lt;/a&gt;, and Suspect Tags, Sentry connects frontend issues to root causes in the backend and vice versa.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgzfsry36iupbld0zq32p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgzfsry36iupbld0zq32p.png" alt="frontend to backend root causes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Trace View gives a waterfall view of a given transaction and dependencies across all projects on a single screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4fozvaqzcaty2v32ffjd.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4fozvaqzcaty2v32ffjd.jpeg" alt="trace view of transactions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Early adopters of our SDK — like Stefen Alper, Co-Founder of eesel, a content search and centralization tool — are already using Sentry for Next.js to capture errors. Now they (and you) can also capture performance data.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"We moved to Next.js for the dev experience and the scalability. As longtime users of Sentry’s error monitoring offering, we’re looking forward to start monitoring performance and managing releases with Sentry’s new Next.js SDK."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;–&lt;em&gt;Stefen Alper, Co-Founder, eesel.app&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Install our &lt;a href="https://sentry.io/for/nextjs" rel="noopener noreferrer"&gt;Next.js SDK&lt;/a&gt; and get a pretty good idea of which commit caused the issue and who is likely responsible, and automatically upload source maps so your stack trace looks like the original code you wrote. Also, &lt;a href="https://us06web.zoom.us/webinar/register/4016233661429/WN_znhaqwALTXWf-3Rqtf8mBQ" rel="noopener noreferrer"&gt;join us&lt;/a&gt; on June 24th, 2021 for a live workshop on how to build, deploy, and monitor Next.js applications with Sentry and Netlify.&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>nextjs</category>
      <category>devops</category>
      <category>performance</category>
    </item>
    <item>
      <title>Performance Monitoring for Android Applications</title>
      <dc:creator>Rahul Chhabria</dc:creator>
      <pubDate>Fri, 19 Mar 2021 01:40:15 +0000</pubDate>
      <link>https://dev.to/sentry/performance-monitoring-for-android-applications-m63</link>
      <guid>https://dev.to/sentry/performance-monitoring-for-android-applications-m63</guid>
      <description>&lt;p&gt;Android is arguably the most ubiquitous operating system in the world. Whether it's a tablet, phone, folding phone, computer, TV, or IoT device, chances are you’ve interacted with Android OS. And to help developers get full visibility into how their customers experience Android's myriad applications, we’re extending &lt;a href="https://docs.sentry.io/platforms/android/performance/" rel="noopener noreferrer"&gt;Performance&lt;/a&gt; to &lt;a href="http://sentry.io/for/android" rel="noopener noreferrer"&gt;Android&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe2ggiexznj4vvmo2lmud.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe2ggiexznj4vvmo2lmud.jpeg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Being an Android developer means thinking through countless scenarios. If something goes wrong, you need to imagine what that support ticket would look like. That means collecting everything from hardware model to software version, in addition to crucial details like screen orientation and battery level. Sentry provides all of that context out of the box — and has done so for some time. And if your users lose their connection while encountering an error, Sentry still captures these events so they can be reported back when the app restarts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuq81gcdlr27i3lg4tlb3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuq81gcdlr27i3lg4tlb3.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But errors are only half of the story. Android developers also need to see how their application performs. That means visibility into when a user writes to disk, fetches media, or loads an object in a given viewport. Without this data, untreated performance problems run the risk of becoming serious errors. But now that you have Sentry’s performance monitoring, you can finally connect your user’s frustrated taps to its poor-performing backend call.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flzxk90wu84wyk8v5avgw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flzxk90wu84wyk8v5avgw.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To install the Android SDK, add it to your &lt;code&gt;build.gradle&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Make sure mavenCentral is there.&lt;/span&gt;
&lt;span class="n"&gt;repositories&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mavenCentral&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Enable Java 1.8 source compatibility if you haven't yet.&lt;/span&gt;
&lt;span class="n"&gt;android&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;compileOptions&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;sourceCompatibility&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JavaVersion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;VERSION_1_8&lt;/span&gt;
        &lt;span class="n"&gt;targetCompatibility&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JavaVersion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;VERSION_1_8&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Add Sentry's SDK as a dependency.&lt;/span&gt;
&lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'io.sentry:sentry-android:4.3.0'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you’ve completed setting up a project in Sentry, we'll give you a value — which we call a DSN or Data Source Name — to add to your &lt;code&gt;AndroidManifest.xml&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;application&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta-data&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"io.sentry.dsn"&lt;/span&gt; &lt;span class="na"&gt;android:value=&lt;/span&gt;&lt;span class="s"&gt;"https://example@sentry.io/example"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/application&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current version of our Android SDK supports &lt;a href="https://docs.sentry.io/platforms/android/performance/included-instrumentation/" rel="noopener noreferrer"&gt;auto instrumentation&lt;/a&gt; activity rendering, which can be expanded on through &lt;a href="https://docs.sentry.io/platforms/android/performance/custom-instrumentation/" rel="noopener noreferrer"&gt;custom instrumentation&lt;/a&gt;. Additionally, in the coming weeks, you’ll be able to automatically create spans for active transactions as well as record &lt;a href="https://docs.sentry.io/platforms/android/enriching-events/breadcrumbs/" rel="noopener noreferrer"&gt;breadcrumbs&lt;/a&gt; for outgoing HTTP requests.&lt;/p&gt;

&lt;p&gt;iOS developers: if you’re getting SDK envy, rest easy knowing that Performance is on the way for you as well. &lt;/p&gt;

&lt;p&gt;Have a friend who wants to manage releases, solve issues faster, and speed up their mobile applications? &lt;a href="https://sentry.io/referrals/mobile/" rel="noopener noreferrer"&gt;Refer&lt;/a&gt; them to Sentry and we’ll give them — and you — three free months on our Team plan.&lt;/p&gt;

&lt;p&gt;To start using Sentry with Android, configure the &lt;a href="https://docs.sentry.io/platforms/android/" rel="noopener noreferrer"&gt;SDK&lt;/a&gt;. And if you’re new to Sentry, you can try it for &lt;a href="https://sentry.io/orgredirect/try-business/" rel="noopener noreferrer"&gt;free&lt;/a&gt; today.&lt;/p&gt;

</description>
      <category>android</category>
      <category>performance</category>
      <category>monitoring</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
