<?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>Forem: Schaeffer</title>
    <description>The latest articles on Forem by Schaeffer (@schaeffer).</description>
    <link>https://forem.com/schaeffer</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%2F589254%2F80713424-d169-477a-bd71-d6fc5933cb9b.png</url>
      <title>Forem: Schaeffer</title>
      <link>https://forem.com/schaeffer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/schaeffer"/>
    <language>en</language>
    <item>
      <title>Distributed Tracing 101 for Full Stack Developers</title>
      <dc:creator>Schaeffer</dc:creator>
      <pubDate>Thu, 12 Aug 2021 21:02:50 +0000</pubDate>
      <link>https://forem.com/sentry/distributed-tracing-101-for-full-stack-developers-50e7</link>
      <guid>https://forem.com/sentry/distributed-tracing-101-for-full-stack-developers-50e7</guid>
      <description>&lt;p&gt;By: &lt;a href="https://blog.sentry.io/authors/ben-vinegar" rel="noopener noreferrer"&gt;Ben Vinegar&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Welcome to Part 1 of our multipart series on Distributed Tracing for Full Stack Developers. In this series, we’ll be learning the ins-and-outs of distributed tracing and how it can assist you in monitoring the increasingly complex requirements of full stack applications.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the early days of the web, writing web applications was simple. Developers generated HTML on the server using a language like PHP, communicated with a single relational database like MySQL, and most interactivity was driven by static HTML form components. While debugging tools were primitive, understanding the execution flow of your code was straightforward.&lt;/p&gt;

&lt;p&gt;In today’s modern web stack it’s anything but. Full stack developers are expected to write JavaScript executing in the browser, interop with multiple database technologies, and deploy server side code on different server architectures (e.g. serverless). Without the right tools, understanding how a user interaction in the browser cascades into a 500 server error deep in your server stack is nigh-impossible. Enter: distributed tracing.&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%2Fw6zg4bp64zizsgt8i0kx.gif" 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%2Fw6zg4bp64zizsgt8i0kx.gif" alt="charlie day"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Me trying to explain a bottleneck in my web stack in 2021.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Distributed tracing&lt;/strong&gt; is a monitoring technique that links the operations and requests occurring between multiple services. This allows developers to “trace” the path of an end-to-end request as it moves from one service to another, letting them pinpoint errors or performance bottlenecks in individual services that are negatively affecting the overall system.&lt;/p&gt;

&lt;p&gt;In this post, we’ll learn more about distributed tracing concepts, go over an end-to-end tracing example in code, and see how to use tracing metadata to add valuable context to your logging and monitoring tools. When we’re done, you’ll not only understand the fundamentals of distributed tracing, but how you can apply tracing techniques to be more effective in debugging your full stack web applications.&lt;/p&gt;

&lt;p&gt;But first, let’s go back to the beginning: what’s distributed tracing again?&lt;/p&gt;

&lt;h1&gt;
  
  
  Distributed tracing basics
&lt;/h1&gt;

&lt;p&gt;Distributed tracing is a method of recording the connected operations of multiple services. Typically, these operations are initiated by requests from one service to another, where a “request” could be an actual HTTP request, or work invoked through a task queue or some other asynchronous means.&lt;/p&gt;

&lt;p&gt;Traces are composed of two fundamental components:&lt;/p&gt;

&lt;p&gt;• A &lt;strong&gt;span&lt;/strong&gt; describes an operation or “work” taking place on a service. Spans can describe broad operations – for example, the operation of a web server responding to an HTTP request – or as granular as a single invocation of a function.&lt;/p&gt;

&lt;p&gt;• A &lt;strong&gt;trace&lt;/strong&gt; describes the end-to-end journey of one or more connected &lt;strong&gt;spans&lt;/strong&gt;. A trace is considered to be a &lt;strong&gt;distributed trace&lt;/strong&gt; if it connects spans (“work”) performed on multiple services.&lt;/p&gt;

&lt;p&gt;Let’s take a look at an example of a hypothetical distributed trace.&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%2Fci4bcaqtoqnu5r2whcym.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%2Fci4bcaqtoqnu5r2whcym.png" alt="distributed trace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The diagram above illustrates how a trace begins in one service – a React application running on the browser – and continues through a call to an API web server, and even further to a background task worker. The spans in this diagram are the work performed within each service, and each span can be “traced” back to the initial work kicked off by the browser application. Lastly, since these operations occur on different services, this trace is considered to be distributed.&lt;/p&gt;

&lt;p&gt;Aside: Spans that describe broad operations (e.g. the full lifecycle of a web server responding to an HTTP request) are sometimes referred to as &lt;strong&gt;transaction spans&lt;/strong&gt; or even just &lt;strong&gt;transactions&lt;/strong&gt;. We’ll talk more about transactions vs. spans in Part 2 of this series.&lt;/p&gt;

&lt;h1&gt;
  
  
  Trace and span identifiers
&lt;/h1&gt;

&lt;p&gt;So far we’ve identified the components of a trace, but we haven’t described how those components are linked together.&lt;/p&gt;

&lt;p&gt;First, each trace is uniquely identified with a &lt;strong&gt;trace identifier&lt;/strong&gt;. This is done by creating a unique randomly generated value (i.e. a UUID) in the &lt;strong&gt;root span&lt;/strong&gt; – the initial operation that kicks off the entire trace. In our example above, the root span occurs in the Browser Application.&lt;/p&gt;

&lt;p&gt;Second, each span first needs to be uniquely identified. This is similarly done by creating a unique &lt;strong&gt;span identifier&lt;/strong&gt; (or &lt;code&gt;span_id&lt;/code&gt;) when the span begins its operation. This &lt;code&gt;span_id&lt;/code&gt; creation should occur at every span (or operation) that takes place within a trace.&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%2Fcr0rii37r7903ro2yt6v.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%2Fcr0rii37r7903ro2yt6v.png" alt="span-id"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s revisit our hypothetical trace example. In the diagram above, you’ll notice that a trace identifier uniquely identifies the trace, and each span within that trace also possesses a unique span identifier.&lt;/p&gt;

&lt;p&gt;Generating &lt;code&gt;trace_id&lt;/code&gt; and &lt;code&gt;span_id&lt;/code&gt; isn’t enough however. To actually connect these services, your application must propagate what’s known as a &lt;strong&gt;trace context&lt;/strong&gt; when making a request from one service to another.&lt;/p&gt;

&lt;h1&gt;
  
  
  Trace context
&lt;/h1&gt;

&lt;p&gt;The trace context is typically composed of just two values:&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Trace identifier&lt;/strong&gt; (or &lt;code&gt;trace_id&lt;/code&gt;): the unique identifier that is generated in the root span intended to identify the entirety of the trace. This is the same trace identifier we introduced in the last section; it is propagated unchanged to every downstream service.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Parent identifier&lt;/strong&gt; (or &lt;code&gt;parent_id&lt;/code&gt;): the span_id of the “parent” span that spawned the current operation.&lt;/p&gt;

&lt;p&gt;The diagram below visualizes how a request kicked off in one service propagates the trace context to the next service downstream. You’ll notice that &lt;code&gt;trace_id&lt;/code&gt; remains constant, while the &lt;code&gt;parent_id&lt;/code&gt; changes between requests, pointing to the parent span that kicked off the latest operation.&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%2F64lkcc8j2357iiw33hwi.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%2F64lkcc8j2357iiw33hwi.png" alt="trace context"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With these two values, for any given operation, it is possible to determine the originating (root) service, and to reconstruct all parent/ancestor services in order that led to the current operation.&lt;/p&gt;

&lt;h1&gt;
  
  
  A working example with code
&lt;/h1&gt;

&lt;p&gt;To understand this all better, let’s actually implement a bare-bones tracing implementation, using the example we’ve been returning to, wherein a browser application is the initiator of a series of distributed operations connected by a trace context.&lt;/p&gt;

&lt;p&gt;First, the browser application renders a form: for the purposes of this example, an “invite user” form. The form has a submit event handler, which fires when the form is submitted. Let’s consider this submit handler our &lt;strong&gt;root span&lt;/strong&gt;, which means that when the handler is invoked, both a &lt;code&gt;trace_id&lt;/code&gt; and &lt;code&gt;span_id&lt;/code&gt; are generated.&lt;/p&gt;

&lt;p&gt;Next, some work is done to gather user-inputted values from the form, then finally a &lt;code&gt;fetch&lt;/code&gt; request is made to our web server to the &lt;code&gt;/inviteUser&lt;/code&gt; API endpoint. As part of this fetch request, the trace context is passed as two custom HTTP headers: &lt;code&gt;trace-id&lt;/code&gt; and &lt;code&gt;parent-id&lt;/code&gt; (which is the current span’s &lt;code&gt;span_id&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="c1"&gt;// browser app (JavaScript)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;traceId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;v4&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spanId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;v4&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Initiate inviteUser POST request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`traceId: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;traceId&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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/v1/inviteUser?email=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;trace-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;traceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;parent-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;spanId&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Success!&lt;/span&gt;&lt;span class="dl"&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;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Something bad happened&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`traceId: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;traceId&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note these are non-standard HTTP headers used for explanatory purposes.&lt;/em&gt; &lt;em&gt;There is an active effort to standardize tracing HTTP headers as part of the W3C &lt;a href="https://www.w3.org/TR/trace-context/" rel="noopener noreferrer"&gt;traceparent&lt;/a&gt; specification, which is still in the “Recommendation” phase.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On the receiving end, the API web server handles the request and extracts the tracing metadata from the HTTP request. It then queues up a job to send an email to the user, and attaches the tracing context as part of a “meta” field in the job description. Last, it returns a response with a 200 status code indicating that the method was successful.&lt;/p&gt;

&lt;p&gt;Note that while the server returned a successful response, the actual “work” isn’t done until the background task worker picks up the newly queued job and actually delivers an email.&lt;/p&gt;

&lt;p&gt;At some point, the queue processor begins working on the queued email job. Again, the trace and parent identifiers are extracted, just as they were earlier in the web server.&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="c1"&gt;// API Web Server&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bull&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emailQueue&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;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v1/inviteUser&lt;/span&gt;&lt;span class="dl"&gt;"&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;res&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spanId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;v4&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;traceId&lt;/span&gt; &lt;span class="o"&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;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;trace-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nx"&gt;parentId&lt;/span&gt; &lt;span class="o"&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;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parent-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Adding job to email queue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`[traceId: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;traceId&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="s2"&gt;`parentId: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;parentId&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="s2"&gt;`spanId: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;spanId&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;emailQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome to our product&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;to&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;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;traceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;traceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

      &lt;span class="c1"&gt;// the downstream span's parent_id is this span's span_id&lt;/span&gt;
      &lt;span class="na"&gt;parentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;spanId&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok&lt;/span&gt;&lt;span class="dl"&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;// Background Task Worker&lt;/span&gt;
&lt;span class="nx"&gt;emailQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spanId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;v4&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;traceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parentId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sending email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`[traceId: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;traceId&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="s2"&gt;`parentId: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;parentId&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="s2"&gt;`spanId: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;spanId&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="c1"&gt;// actually send the email&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="nf"&gt;done&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;em&gt;If you’re interested in running this example yourself, you can find the source code on &lt;a href="https://github.com/getsentry/distributed-tracing-examples" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Logging with distributed systems
&lt;/h1&gt;

&lt;p&gt;You’ll notice that at every stage of our example, a logging call is made using console.log that additionally emits the current &lt;strong&gt;trace&lt;/strong&gt;, &lt;strong&gt;span&lt;/strong&gt;, and &lt;strong&gt;parent&lt;/strong&gt; identifiers. In a perfect synchronous world – one where each service could log to the same centralized logging tool – each of these logging statements would appear sequentially:&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%2Ft02v22mxwjgy2nwpfwuj.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%2Ft02v22mxwjgy2nwpfwuj.png" alt="identifiers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If an exception or errant behavior occurred during the course of these operations, it would be relatively trivial to use these or additional logging statements to pinpoint a source. But the unfortunate reality is that these are &lt;em&gt;distributed services&lt;/em&gt;, which means:&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Web servers typically handle many concurrent requests&lt;/strong&gt;. The web server may be performing work (and emitting logging statements) attributed to other requests.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Network latency can cloud the order of operations&lt;/strong&gt;. Requests made from upstream services might not reach their destination in the same order they were fired.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Background workers may have queued jobs&lt;/strong&gt;. Workers may have to first work through earlier queued jobs before reaching the exact job queued up in this trace.&lt;/p&gt;

&lt;p&gt;In a more realistic example, our logging calls might look something like this, which reflects multiple operations occurring concurrently:&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%2Fl46dbkbrt3cxy7s7pb29.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%2Fl46dbkbrt3cxy7s7pb29.png" alt="logging calls"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Without tracing metadata, understanding the topology of which action invoked which action would be impossible. But by emitting tracing meta information at every logging call, it’s possible to quickly filter on all logging calls within a trace by filtering on &lt;code&gt;traceId&lt;/code&gt;, and to reconstruct the exact order by examining &lt;code&gt;spanId&lt;/code&gt; and &lt;code&gt;parentId&lt;/code&gt; relationships.&lt;/p&gt;

&lt;p&gt;This is the power of distributed tracing: by attaching metadata describing the current operation (span id), the parent operation that spawned it (parent id), and the trace identifier (trace id), we can augment logging and telemetry data to better understand the exact sequence of events occurring in your distributed services.&lt;/p&gt;

&lt;h1&gt;
  
  
  Tracing in the real world
&lt;/h1&gt;

&lt;p&gt;Over the course of this article, we have been working with a somewhat contrived example. In a real distributed tracing environment, you wouldn’t generate and pass all your span and tracing identifiers manually. Nor would you rely on &lt;code&gt;console.log&lt;/code&gt; (or other logging) calls to emit your tracing metadata yourself. You would use proper tracing libraries to handle the instrumentation and emitting of tracing data for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenTelemetry
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opentelemetry.io/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt; is a collection of open source tools, APIs, and SDKs for instrumenting, generating, and exporting telemetry data from running software. It provides language-specific implementations for most popular programming languages, including both browser &lt;a href="https://github.com/open-telemetry/opentelemetry-js" rel="noopener noreferrer"&gt;JavaScript and Node.js&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt; is an open source application monitoring product that helps you identify errors and performance bottlenecks in your code. It provides client libraries in every major programming language which instrument your software’s code to capture both error data and tracing telemetry.&lt;/p&gt;

&lt;p&gt;Sentry uses this telemetry in a number of ways. For example, Sentry’s &lt;a href="https://sentry.io/for/performance/" rel="noopener noreferrer"&gt;Performance Monitoring&lt;/a&gt; feature set uses tracing data to generate waterfall diagrams that illustrate the end-to-end latency of your distributed services’ operations within a trace.&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%2F507ybps6z28oihzm5yvd.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%2F507ybps6z28oihzm5yvd.png" alt="performance monitoring"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sentry additionally uses tracing metadata to augment its Error Monitoring capabilities to understand how an error triggered in one service (e.g. server backend) can propagate to an error in another service (e.g. frontend).&lt;/p&gt;

&lt;p&gt;You can learn more about &lt;a href="https://sentry.io/features/distributed-tracing/" rel="noopener noreferrer"&gt;Sentry and distributed tracing here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Next: Span trees, OpenTelemetry, and Sentry
&lt;/h1&gt;

&lt;p&gt;Congratulations: you’ve graduated from part 1 of our series on distributed tracing for full stack developers. At this point you should understand what a trace is, how trace information is propagated through services via tracing context, and how trace context can be used to connect logging statements between multiple distributed services.&lt;/p&gt;

&lt;p&gt;Stay tuned for the next post in this series, where we’ll expand on the OpenTelemetry set of tracing tools, and take a further dive into how Sentry incorporates tracing context to augment error and performance monitoring telemetry.&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>performance</category>
      <category>distributedsystems</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Instrumenting Our Frontend Test Suite (...and fixing what we found)</title>
      <dc:creator>Schaeffer</dc:creator>
      <pubDate>Tue, 27 Jul 2021 18:39:32 +0000</pubDate>
      <link>https://forem.com/sentry/instrumenting-our-frontend-test-suite-and-fixing-what-we-found-55</link>
      <guid>https://forem.com/sentry/instrumenting-our-frontend-test-suite-and-fixing-what-we-found-55</guid>
      <description>&lt;p&gt;By: &lt;a href="https://blog.sentry.io/authors/billy-vong"&gt;Billy Vong&lt;/a&gt; &amp;amp; &lt;a href="https://blog.sentry.io/authors/scott-cooper"&gt;Scott Cooper&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Adding Instrumentation to Jest
&lt;/h1&gt;

&lt;p&gt;Here at Sentry, we like to dogfood our product as much as possible. Sometimes, it results in unusual applications of our product and sometimes these unusual applications pay off in a meaningful way. In this blog post, we’ll examine one such case where we use the Sentry JavaScript SDK to instrument &lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt; (which runs our frontend test suite) and how we addressed the issues that we found.&lt;/p&gt;

&lt;p&gt;We have high-level metrics for how well (or not) our CI is performing. This is useful to get a general sense of the health of the system, however, it does not help when doing a deeper dive into why a system is slow. We decided to instrument our CI jobs at the test runner level in order to get insights into the performance of the individual tests. We needed to be able to see if our test suite was slowing down because we were adding more tests or if it was because we had poor-performing tests.&lt;/p&gt;

&lt;p&gt;As mentioned above, we use Jest as our test runner. It’s important to note, that our instrumentation method requires using &lt;a href="https://github.com/facebook/jest/blob/master/packages/jest-circus/README.md"&gt;jest-circus&lt;/a&gt; test runner. This is the default for Jest version 27, however, it can be used with earlier versions of Jest. &lt;code&gt;jest-circus&lt;/code&gt; is required because it allows us to listen to events from Jest by using a &lt;a href="https://jestjs.io/docs/configuration#testenvironment-string"&gt;custom environment&lt;/a&gt; and defining a &lt;code&gt;handleTestEvent&lt;/code&gt; method. Below is a basic test snippet annotated with an approximation of where &lt;code&gt;jest-circus&lt;/code&gt;’s events are fired.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// --&amp;gt; setup
// --&amp;gt; add_hook&amp;lt;beforeEach&amp;gt;
// --&amp;gt; start_describe_definition
// --&amp;gt; add_hook&amp;lt;beforeAll&amp;gt;
// --&amp;gt; add_hook&amp;lt;beforeEach&amp;gt;
// --&amp;gt; add_hook&amp;lt;afterEach&amp;gt;
// --&amp;gt; add_hook&amp;lt;afterAll&amp;gt;
// --&amp;gt; add_test
// --&amp;gt; finish_describe_definition
// --&amp;gt; run_start
describe('describe', function () {
  // --&amp;gt; run_describe_start
  // --&amp;gt; hook_start&amp;lt;beforeAll&amp;gt;
  // --&amp;gt; hook_success&amp;lt;beforeAll&amp;gt;

  beforeAll(function () {});
  beforeEach(function () {});
  afterEach(function () {});
  afterAll(function () {});

  // --&amp;gt; test_start
  // --&amp;gt; hook_start&amp;lt;beforeEach&amp;gt;
  // --&amp;gt; hook_success&amp;lt;beforeEach&amp;gt;
  it('test', function () {
    // --&amp;gt; test_fn_start
    expect(true).toBe(true);
  }); // --&amp;gt; test_fn_success
  // --&amp;gt; hook_start&amp;lt;afterEach&amp;gt;
  // --&amp;gt; hook_success&amp;lt;afterEach&amp;gt;
  // --&amp;gt; test_done

  // --&amp;gt; hook_start&amp;lt;afterAll&amp;gt;
  // --&amp;gt; hook_success&amp;lt;afterAll&amp;gt;
}); // --&amp;gt; run_describe_finish
// --&amp;gt; run_finish
// --&amp;gt; teardown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.sentry.io/platforms/javascript/performance/instrumentation/custom-instrumentation/"&gt;Sentry’s Performance Monitoring instrumentation&lt;/a&gt; is tree-like in structure with a transaction as the root node, and spans as child nodes. We can use a combination of the event name and the test name to determine if we should either create a transaction, create a child span from an existing transaction or end a span/transaction. Here’s an &lt;a href="https://github.com/getsentry/sentry/blob/master/tests/js/instrumentedEnv/index.js"&gt;example of how we implemented it&lt;/a&gt;.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  Fixing Performance Issues
&lt;/h1&gt;

&lt;p&gt;Starting an investigation into a slow Jest test is a bit like peering into a black box because there’s so much abstraction. So the first steps are - find out which tests to look at and then find out what they’re spending time doing. The slowest tests were all larger page views with many subcomponents and components that access data from our data stores. Bigger components should be slower, right?&lt;/p&gt;

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

&lt;p&gt;Looking at the “alert rules index” test we can see that the &lt;code&gt;beforeEach&lt;/code&gt; function is getting slower after every test, yet the code in the &lt;code&gt;beforeEach&lt;/code&gt; block remains the same and should take just as much time on the first test as it does on the last test. Looking at what is in the &lt;code&gt;beforeEach&lt;/code&gt; function wasn’t enough to decide what was slowing it down. &lt;code&gt;beforeEach&lt;/code&gt; contained a few api mocks and a call to &lt;code&gt;ProjectsStore.loadInitialData([]);&lt;/code&gt; that was initializing the project store with an empty array. But none of that should be slow, so let’s have node tell us what is happening.&lt;/p&gt;

&lt;p&gt;Start jest using the node debugger on just the one test file. The &lt;code&gt;--inspect-brk&lt;/code&gt; flag tells node to wait until we have attached our profiler.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node --inspect-brk node_modules/.bin/jest --runInBand --no-cache tests/js/spec/views/alerts/rules/index.spec.jsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;After profiling the test and zooming into the area where the tests are running, we located the code that runs during the &lt;code&gt;beforeEach&lt;/code&gt;. In this screenshot, we’ve renamed the anonymous &lt;code&gt;beforeEach&lt;/code&gt; function to a named function &lt;code&gt;badBeforeEach&lt;/code&gt; so it can be found easier. Below it is one call to &lt;code&gt;ProjectsStore.loadInitialData&lt;/code&gt; followed by several calls to &lt;code&gt;setState&lt;/code&gt; and a bunch of react work being kicked off. This shouldn’t be happening as there shouldn’t be a component listening to the store yet.&lt;/p&gt;

&lt;p&gt;This led us to check that the component was being unmounted after each test. We added a &lt;code&gt;componentWillUnmount&lt;/code&gt; to the class component being tested with a &lt;code&gt;console.log&lt;/code&gt; inside to see if it was being called. When we did not see the &lt;code&gt;console.log&lt;/code&gt;, we tested it manually by unmounting the component after every test.&lt;/p&gt;

&lt;p&gt;The result: our &lt;code&gt;beforeEach&lt;/code&gt; function takes a fraction of a second every iteration instead of running change detection in components that should have been unmounted and our test has its overall total time cut nearly in half. The &lt;a href="https://enzymejs.github.io/enzyme/docs/api/mount.html"&gt;enzyme docs&lt;/a&gt; warn you that calling &lt;code&gt;mount&lt;/code&gt; instead of &lt;code&gt;shallowMount&lt;/code&gt; will persist components in the DOM and the reason we’re seeing a larger impact on this test is because the store is still triggering change detection in components that should’ve been destroyed.&lt;/p&gt;

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

&lt;p&gt;Using Sentry’s Performance Trends feature, we can confirm the performance impact these changes made to the tests.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  Instrumenting your own tests
&lt;/h1&gt;

&lt;p&gt;We provided an example &lt;a href="https://github.com/billyvg/jest-sentry-environment"&gt;Jest environment&lt;/a&gt; that you can use if you’re interested in instrumenting your own Jest tests. You’ll need to install the &lt;code&gt;jest-sentry-environment&lt;/code&gt; package and update your Jest configuration to use the fore-mentioned package, as well as supplying your Sentry DSN. For further instructions, please see the &lt;a href="https://github.com/billyvg/jest-sentry-environment"&gt;repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>monitoring</category>
      <category>devops</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How we updated our Content Security Policy using our own product</title>
      <dc:creator>Schaeffer</dc:creator>
      <pubDate>Thu, 27 May 2021 19:12:12 +0000</pubDate>
      <link>https://forem.com/sentry/how-we-updated-our-content-security-policy-using-our-own-product-4mgh</link>
      <guid>https://forem.com/sentry/how-we-updated-our-content-security-policy-using-our-own-product-4mgh</guid>
      <description>&lt;p&gt;By: &lt;a href="https://blog.sentry.io/authors/mark-story"&gt;Mark Story&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everyone talks about creating content. But what about controlling it? In this Dogfooding Chronicle, I’ll walk you through how we updated our Content Security Policy using &lt;a href="https://docs.sentry.io/product/discover-queries/"&gt;Discover&lt;/a&gt; — and how we’re keeping watch with &lt;a href="https://sentry.io/features/dashboards/"&gt;Dashboards&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Defining Content Security Policies
&lt;/h1&gt;

&lt;p&gt;As the Internet evolves, so must our security mechanisms. It’s why same-origin policies (a Netscape-era directive in which resources from one origin are unable to modify or read data from another origin) put your application at risk for clickjacking and cross-site scripting attacks. That’s because same-origin policies have a weakness that enables third-party entities to have the same access rights as trusted entities. Content Security Policies (CSPs) address this blind spot by approving specific content types — everything from &lt;a href="http://sentry.io/for/javascript"&gt;JavaScript&lt;/a&gt; sources to CSS fonts, HTML frames and embeddable Java applets.&lt;/p&gt;

&lt;p&gt;Put simply, a CSP lets a browser know which content sources are to be trusted — and which aren’t. And when there is a CSP violation, browsers can submit the error to a &lt;code&gt;report-uri&lt;/code&gt;. What’s more, if you have Sentry, these violation reports are integrated into your application’s monitoring dashboards.&lt;/p&gt;

&lt;p&gt;During our most recent penetration test, we found that some of the CDN domains in our allow list were hosting potentially dangerous scripts. One of our recommendations from this audit was to refine and improve our CSP rules.&lt;/p&gt;

&lt;h1&gt;
  
  
  Discover-ing ‘report-only’ mode
&lt;/h1&gt;

&lt;p&gt;Content Security Policies have two modes. The first mode enforces and actively blocks resource loading and execution, while the second collects the errors that would happen if the rules were active. This mode is set via the invaluable &lt;code&gt;Content-Security-Policy-Report-Only&lt;/code&gt; header that defines your CSP rules.&lt;/p&gt;

&lt;p&gt;When I combined this ‘report-only’ feature with &lt;a href="http://sentry.io/features/custom-queries"&gt;Discover queries&lt;/a&gt;, I was able to visualize all the errors coming from &lt;code&gt;report-only&lt;/code&gt; mode. Now I could view the impact of fixes in real time, and see which rules were being broken — without disrupting customers.&lt;/p&gt;

&lt;h1&gt;
  
  
  Sussing out Safari
&lt;/h1&gt;

&lt;p&gt;While &lt;code&gt;report-only&lt;/code&gt; mode worked well, I did encounter some hiccups in Safari, as it’s been known to jumble its report-only and enforced modes. This meant I was getting errors for sources that should be allowed. To verify that these errors were caused by Safari bugs, I used our staging environment to test Safari with only the new rules being enforced. And using &lt;a href="https://docs.sentry.io/product/dashboards/custom-dashboards/"&gt;Dashboards&lt;/a&gt;, I was able to fully view Safari’s impact on our CSP errors.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  A glance at the solution
&lt;/h1&gt;

&lt;p&gt;Like reading a good detective novel, once I solved this problem, another, more confounding problem bubbled up to take its place. Our Jira integration was having trouble integrating with our CSP. Because our &lt;a href="http://sentry.io/integrations/jira"&gt;Jira integration&lt;/a&gt; includes an embedded ‘glance view’, I could view Sentry data alongside other fields like assignee, priority and labels. After reviewing these embedded views, I discovered the issue: outdated libraries that relied on &lt;code&gt;eval()&lt;/code&gt; expressions which are not allowed in our CSP rules.&lt;/p&gt;

&lt;p&gt;While upgrading these libraries to newer versions resolved a large number of errors, it didn’t solve them all. And though I was unable to reproduce these last few errors, I was able to use Discover’s tag breakdowns to narrow things back down to — you guessed it — a Safari-specific issue:&lt;/p&gt;

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

&lt;h1&gt;
  
  
  CSP 3: Coming to a browser near you
&lt;/h1&gt;

&lt;p&gt;The newest iteration of CSP has been in production for years, and is about to be fully released. Which is why we have defined rules for all three levels to get the broadest — and sturdiest — coverage possible. Doing so has both simplified our CSP configuration and improved our resilience to cross-site scripting. Which brings me to my last tip: it’s important to define directives in the order of their levels. Browsers will often stop parsing a directive when they find something they don’t know, so it’s important that there are specific directives for all three CSP levels.&lt;/p&gt;

&lt;p&gt;Developing a Content Security Policy isn’t about being fussy — it’s about being functional. Data and code from untrusted sources should not have the same privileges as the trusted programmer’s code. By implementing a well-defined CSP, you can mitigate attacks, control assets, and breathe easier.&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>security</category>
    </item>
    <item>
      <title>Great Moments in Application Monitoring</title>
      <dc:creator>Schaeffer</dc:creator>
      <pubDate>Fri, 21 May 2021 19:53:17 +0000</pubDate>
      <link>https://forem.com/sentry/great-moments-in-application-monitoring-4ba4</link>
      <guid>https://forem.com/sentry/great-moments-in-application-monitoring-4ba4</guid>
      <description>&lt;p&gt;The consequences of poor application performance are both real and terrifying: lost customers, lost trust from your team, and lost confidence in your abilities. It’s why every developer remembers those moments where their solution improves both process and performance. We’ve seen this happen firsthand with our customers, and want to share their Great Moments in Application Monitoring:&lt;/p&gt;

&lt;h1&gt;
  
  
  DoorDash
&lt;/h1&gt;

&lt;p&gt;When you’re connecting hungry customers with hurried drivers, you can’t afford to have debugging eat up development time. Unfortunately, whenever a &lt;a href="https://sentry.io/customers/doordash/"&gt;DoorDash&lt;/a&gt; developer tried something new without informing the larger team, resources would be wasted debugging their unknown logic. With Sentry’s automated stack traces, however, DoorDash developers could finally — and clearly — understand their colleagues’ code. By including &lt;code&gt;$request_id&lt;/code&gt; in their access logs, passing that value downstream to their application servers, and then modifying the application’s entry point, DoorDash was able to utilize &lt;code&gt;$request_id&lt;/code&gt; as a logging variable. Now, whenever there’s an error, DoorDash can search $request_id and immediately see the exact issue and exception. The result? Scalable production, the ability to trace every request end‑to‑end, and a process that’s as cool as a cucumber bisque.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/LVVKvjUaCSg"&gt;
&lt;/iframe&gt;
&lt;br&gt;
&lt;em&gt;"As the one service that’s consistently able to catch these types of NGINX variables, Sentry is really valuable at tracing exceptions of errors. And by providing automatic feedback, we’re able to see things clearly and gain a better understanding of what’s going wrong."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;- Zhaobang Liu, DevOps Engineer, DoorDash&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Eventbrite
&lt;/h1&gt;

&lt;p&gt;For event marketer &lt;a href="https://sentry.io/customers/eventbrite/"&gt;Eventbrite&lt;/a&gt;, their errors were talking over one another, as their team was constantly being distracted by alerts on duplicate issues. So they hired Sentry to emcee their errors. After instrumenting Sentry’s &lt;a href="https://docs.sentry.io/product/sentry-basics/guides/grouping-and-fingerprints/"&gt;grouping and merging&lt;/a&gt; features, the Eventbrite team was able to create a list of errors prioritized by user impact and frequency. Unlike triaging in the past — where the Eventbrite team had to parse through countless duplicate issues — now they were only focused on solving what mattered most. What’s more, using &lt;a href="https://docs.sentry.io/product/releases/"&gt;Release Health&lt;/a&gt;, Eventbrite improved the quality of their releases by gauging adoption levels as well as the percentage of both crash-free sessions and crash-free users.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Sentry helps our team fix the most important issues in each release. We can track how a release is trending by percent of crash-free sessions. With this data, we can remediate issues that impact the most users and move on to building more features."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;- Jaylum Chen, Staff Software Engineer at Eventbrite&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Cloudflare
&lt;/h1&gt;

&lt;p&gt;The year was 2019. And &lt;a href="https://sentry.io/customers/cloudflare/"&gt;Cloudflare&lt;/a&gt;’s release process consisted of manually tagging a release and then manually running through their dashboard to find errors. Not only was this process tedious, it made the Cloudflare team susceptible to errors and redundant work. But with Sentry’s &lt;a href="https://docs.sentry.io/product/releases/"&gt;Releases&lt;/a&gt;, the Cloudflare team was able to part the skies of gloomy manual work. By setting up an automated release plan, the Cloudflare team could now safely deploy code, monitor every step of their deployment process, and link all releases back to the Releases page. When automated tools — not manual brains — do the work, both Cloudflare’s code and their customers win.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/JiOMNx-2oOo"&gt;
&lt;/iframe&gt;
&lt;br&gt;
&lt;em&gt;"Sometimes errors on the front-end have roots on the backend. We use Sentry’s tags and metadata about a request that comes in to pass along a version of distributed tracing to link these back."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;- Tony Stuck, Engineering Manager, Cloudflare&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Volley
&lt;/h1&gt;

&lt;p&gt;With trivia games like Song Quiz, Yes Sire, and Are You Smarter Than a 5th Grader?, &lt;a href="https://sentry.io/customers/volley/"&gt;Volley&lt;/a&gt; entertains millions of families on smart speaker devices every month. But with all those players blurting out answers at their voice-controlled devices, Volley engineers knew that they needed complete visibility into their code so they could properly scale their product.&lt;/p&gt;

&lt;p&gt;Using our &lt;a href="https://docs.sentry.io/product/performance/"&gt;performance monitoring&lt;/a&gt; offering, the Volley team observed that their voice-controlled LaunchRequest was waiting for an API call to update the user’s subscription status. Once Volley moved this call to only those sections of the code which needed it, they noticed an improved Apdex score, faster user interactions, and their time to launch was cut in half.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Sentry has become part of my daily routine. Every morning, I check to see if there are any new issues, and look at yesterday’s performance to see if any of our latest changes affect it negatively or positively."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;- Devin Ozel, Sr. Software Engineer, Volley&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With Great Moments in Application Monitoring, we recognize those intrepid developers who were able to get their application to perform the way it was intended - and the way their customers demand.&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>performance</category>
      <category>devops</category>
    </item>
    <item>
      <title>Supporting Native Android Libraries Loaded From APKs</title>
      <dc:creator>Schaeffer</dc:creator>
      <pubDate>Thu, 13 May 2021 21:37:41 +0000</pubDate>
      <link>https://forem.com/sentry/supporting-native-android-libraries-loaded-from-apks-119p</link>
      <guid>https://forem.com/sentry/supporting-native-android-libraries-loaded-from-apks-119p</guid>
      <description>&lt;p&gt;By: &lt;a href="https://blog.sentry.io/authors/arpad-borsos"&gt;Arpad Borsos&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Like mechanics who restore their own cars or plastic surgeons who self-rhinoplasty, our developers put their skills to interesting uses during their free time. Here, Native Platform Engineer Arpad Borsos breaks down how memory mappings and dynamic library loading works and how it relates to native Android libraries loaded from APKs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Libraries are key to modular programming, as they offer functionality in a single unit which can be shared with other developers. As you’re no doubt aware, there are two types of libraries: static and dynamic. Static libraries are directly embedded into your application at build time, while dynamic libraries are linked when starting the application, or at any later point.&lt;/p&gt;

&lt;p&gt;This concept is important because dynamic libraries can be updated without modifying the application itself for things like fixing security issues or improving performance. Applications can also be split into multiple, dynamically-loaded libraries for organizational reasons, or when an application consists of a User Interface and a separate service running in the background.&lt;/p&gt;

&lt;p&gt;When working with the Android NDK, native libraries written in C, Rust, or similar low-level languages are loaded dynamically by the Java layer. That is how &lt;code&gt;sentry-native&lt;/code&gt; gets loaded into Android Apps that use the NDK.&lt;/p&gt;

&lt;p&gt;Usually, these libraries are individual files on disk. You might have seen a few of these “.dll” files next to Windows Applications, which are similar on other operating systems. The dynamic loader on Android, which is itself a system library, has the ability to load libraries directly from Android “.apk” packages without needing to first extract them to disk. This is quite beneficial, as it saves precious disk space on your mobile device.&lt;/p&gt;

&lt;p&gt;So far, &lt;code&gt;sentry-native&lt;/code&gt;, and in turn, NDK support for our Android SDK, relied on these files being extracted to disk. This has created a lot of friction — especially for new customers — as newer Android Versions no longer extract “.apk” packages by default. To get around this, application developers had to set some explicit configuration flags, many of which were frequently overlooked when setting up Sentry for Android.&lt;/p&gt;

&lt;p&gt;Starting with version 5 of our Android SDK, we added support for libraries loaded from “.apk”, which will remove the need to change configuration flags while also improving disk space usage of Apps that use the Sentry SDK.&lt;/p&gt;

&lt;p&gt;Let us take a deeper dive into how all of this works, and what we need to change in order to support this use case.&lt;/p&gt;

&lt;h1&gt;
  
  
  Which libraries are loaded?
&lt;/h1&gt;

&lt;p&gt;On most platforms, you can query the list of loaded libraries directly from the dynamic loader via an API. For example, Windows has the &lt;a href="https://docs.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot"&gt;Tool Help Library&lt;/a&gt;, and on Apple platforms there are some &lt;a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dyld.3.html"&gt;&lt;code&gt;dyld&lt;/code&gt;&lt;/a&gt; functions available. Unfortunately, Linux has no standardized userspace tools. While GNU/Linux has the &lt;a href="https://man7.org/linux/man-pages/man3/dl_iterate_phdr.3.html"&gt;&lt;code&gt;dl_iterate_phdr function&lt;/code&gt;&lt;/a&gt;, it is notably not available on older Android systems (&lt;a href="https://android.googlesource.com/platform/bionic/+/master/docs/status.md"&gt;Bionic Status&lt;/a&gt; lists the API as available starting with API 21, aka Android 5, released at the end of 2014). That means in order to support ancient Android versions, you will need to get the list of loaded libraries from somewhere else.&lt;/p&gt;

&lt;p&gt;Standard practice here is to find the mapped ELF files by parsing the memory map info from /proc/XXX/maps. This is what &lt;a href="https://chromium.googlesource.com/breakpad/breakpad/+/master/src/client/linux/minidump_writer/linux_dumper.cc#541"&gt;Breakpad&lt;/a&gt; &lt;a href="https://chromium.googlesource.com/breakpad/breakpad/+/master/src/processor/proc_maps_linux.cc#29"&gt;(in two places)&lt;/a&gt;, &lt;a href="https://chromium.googlesource.com/crashpad/crashpad/+/refs/heads/master/util/linux/memory_map.cc#57"&gt;Crashpad&lt;/a&gt;, &lt;a href="https://cs.android.com/android/platform/superproject/+/master:system/libprocinfo/include/procinfo/process_map.h;drc=master;l=92"&gt;Android’s libunwindstack&lt;/a&gt;, and &lt;a href="https://github.com/llvm/llvm-project/blob/62ec4ac90738a5f2d209ed28c822223e58aaaeb7/lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp#L26"&gt;LLDB&lt;/a&gt; do. In my opinion, they take this approach because they’re outside observers in the sense that they just can’t query the dynamic loader from inside the process.&lt;/p&gt;

&lt;p&gt;This approach makes sense: it’s how I approached loading libraries for &lt;code&gt;sentry-native&lt;/code&gt;. That said, you have to be careful to cover all cases — notably .so files loaded directly from inside Android .apk packages. And so, I went about finding ways to support these .apk cases.&lt;/p&gt;

&lt;h1&gt;
  
  
  The /proc/X/maps format
&lt;/h1&gt;

&lt;p&gt;In Linux, all the executables and libraries are ELF files. &lt;a href="https://blog.cloudflare.com/how-to-execute-an-object-file-part-1/"&gt;Cloudflare&lt;/a&gt; has a great tutorial on how a loader parses and processes these ELF files. The documentation for the &lt;code&gt;/proc/X/maps&lt;/code&gt; output format is described in this &lt;a href="https://man7.org/linux/man-pages/man5/proc.5.html"&gt;manpage&lt;/a&gt;. The format includes the start/end of the virtual address space, permission information, information about the inode (file), and the offset inside that file.&lt;/p&gt;

&lt;p&gt;While there are cases when a library needs just one mapping, most of the time, it’s split into two or more mappings. Usually that consists of a read-only mapping that includes the ELF headers and metadata, and an executable mapping that holds the actual program code. On my Linux system, I saw up to six mappings for a single file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;7f8cd3467000-7f8cd3475000 r--p 00000000 00:1c 7597971                    /usr/lib/libcurl.so.4.7.0
7f8cd3475000-7f8cd34da000 r-xp 0000e000 00:1c 7597971                    /usr/lib/libcurl.so.4.7.0
7f8cd34da000-7f8cd34f6000 r--p 00073000 00:1c 7597971                    /usr/lib/libcurl.so.4.7.0
7f8cd34f6000-7f8cd34f7000 ---p 0008f000 00:1c 7597971                    /usr/lib/libcurl.so.4.7.0
7f8cd34f7000-7f8cd34fa000 r--p 0008f000 00:1c 7597971                    /usr/lib/libcurl.so.4.7.0
7f8cd34fa000-7f8cd34fc000 rw-p 00092000 00:1c 7597971                    /usr/lib/libcurl.so.4.7.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting case here is that the fourth mapping is not readable, and basically creates a gap in the address space.&lt;/p&gt;

&lt;p&gt;These two mappings load the exact same libraries, once extracted to disk, once directly from the apk:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```77a85dbda000-77a85dbdd000 r-xp 00000000 fd:05 40992                      /data/app/x/y/lib/x86_64/libsentry-android.so&lt;br&gt;
77a85dbdd000-77a85dbde000 ---p 00000000 00:00 0&lt;br&gt;
77a85dbde000-77a85dbdf000 r--p 00003000 fd:05 40992                      /data/app/x/y/lib/x86_64/libsentry-android.so&lt;br&gt;
77a85dc15000-77a85dd6c000 r-xp 00000000 fd:05 40991                      /data/app/x/y/lib/x86_64/libsentry.so&lt;br&gt;
77a85dd6c000-77a85dd6d000 ---p 00000000 00:00 0&lt;br&gt;
77a85dd6d000-77a85dd79000 r--p 00157000 fd:05 40991                      /data/app/x/y/lib/x86_64/libsentry.so&lt;br&gt;
77a85dd79000-77a85dd7a000 rw-p 00163000 fd:05 40991                      /data/app/x/y/lib/x86_64/libsentry.so&lt;/p&gt;

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




```77a85dbf0000-77a85dbf3000 r-xp 00001000 fd:05 40977                      /data/app/x/y/base.apk
77a85dbf3000-77a85dbf4000 ---p 00000000 00:00 0
77a85dbf4000-77a85dbf5000 r--p 00004000 fd:05 40977                      /data/app/x/y/base.apk
77a85dc15000-77a85dd6c000 r-xp 00006000 fd:05 40977                      /data/app/x/y/base.apk
77a85dd6c000-77a85dd6d000 ---p 00000000 00:00 0
77a85dd6d000-77a85dd79000 r--p 0015d000 fd:05 40977                      /data/app/x/y/base.apk
77a85dd79000-77a85dd7a000 rw-p 00169000 fd:05 40977                      /data/app/x/y/base.apk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mappings are basically the same — it’s just that in the case of the base .apk, the file offsets are different. And the Android loader, again, inserts a non-readable gap in between.&lt;/p&gt;

&lt;h1&gt;
  
  
  So how do we get the library list from there?
&lt;/h1&gt;

&lt;p&gt;So far, the sentry-native modulefinder implementation has been a bit too conservative. Because of concerns reading arbitrary memory, we mmap-ed the file into memory and tried to extract ELF headers. Unfortunately, that approach doesn’t work with those libraries that are loaded directly from apk files, as the ELF headers are at a certain offset in that file. Plus, as we demonstrated above, some issues related to non-contiguous mappings and double mappings caused problems in the old implementation, as it worked based on the filename that it saw.&lt;/p&gt;

&lt;p&gt;So my new approach is to keep track of readable mappings, their file offsets, and the gaps between them. For each readable mapping, I am looking for the magic ELF signature. If I find one, I process the previously saved mappings, while also taking care of possible duplicates.&lt;/p&gt;

&lt;p&gt;This approach still has unanswered questions. One is trying to read arbitrary memory. I think I’m pretty safe as I’m only considering readable mappings, but one improvement would be to use &lt;a href="https://dev.toprocess_vm_readv"&gt;&lt;code&gt;process_vm_readv&lt;/code&gt;&lt;/a&gt; here. That said, I have also seen problems with using that mapping on Android. Another potential issue is how to correctly deal with mappings which have gaps in them, or even ones that appear multiple times. The ELF file might instruct the loader to load the executable code at a different offset to the ELF header in RAM than the offset on disk — or it might not. It very much depends on how well we use this information to post-process crash reports.&lt;/p&gt;

&lt;p&gt;This problem is not unique to the way that &lt;code&gt;sentry-native&lt;/code&gt; used to read the list of libraries. We also saw some breakpad tools get this wrong by creating minidumps with invalid mappings that fail later on in the post-processing pipeline.&lt;/p&gt;

&lt;p&gt;Loading libraries is a non-trivial problem, and I am confident that I’m not the only one struggling with it. And make no mistake: it’s some work to investigate failures and patch the relevant code for such a specific use case. But with Android adoption picking up more and more traction, it’s necessary work that will save space for your user - and stress on yourself.&lt;/p&gt;

</description>
      <category>android</category>
      <category>native</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Slow and Steady: Converting Sentry’s Entire Frontend to TypeScript</title>
      <dc:creator>Schaeffer</dc:creator>
      <pubDate>Tue, 13 Apr 2021 19:59:24 +0000</pubDate>
      <link>https://forem.com/sentry/slow-and-steady-converting-sentry-s-entire-frontend-to-typescript-3g17</link>
      <guid>https://forem.com/sentry/slow-and-steady-converting-sentry-s-entire-frontend-to-typescript-3g17</guid>
      <description>&lt;p&gt;Recently, Sentry converted 100% of its frontend React codebase from JavaScript to TypeScript. This year-long effort spanned over a dozen members of the engineering team, 1,100 files, and 95,000 lines of code.&lt;/p&gt;

&lt;p&gt;In this blog post, we share our process, techniques, challenges, and ultimately, what we learned along this journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pitch
&lt;/h2&gt;

&lt;p&gt;Back in 2019, we were shipping more frontend bugs than what was acceptable. After looking at the underlying causes of these incidents, it became clear that many of these bugs could have been prevented by static analysis and type checking.&lt;/p&gt;

&lt;p&gt;During that year’s Hackweek event, Lyn Nagara, Alberto Leal, and Daniel Griesser pitched introducing TypeScript to the Sentry frontend. This team bootstrapped the TypeScript compiler to our build process as well as converted a few non-trivial views — and their related components — to TypeScript.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hackweek is an event that takes place once a year, giving all Sentry employees the opportunity to set aside their usual work to focus solely on innovative projects and ideas. Hackweek has given birth to numerous applications and tools that are now important parts of our product, like the recently launched &lt;a href="https://blog.sentry.io/2021/03/16/building-dark-mode" rel="noopener noreferrer"&gt;Dark Mode project&lt;/a&gt;.&lt;/em&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%2Fyl6lu1bktyvwpokvez9f.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%2Fyl6lu1bktyvwpokvez9f.jpeg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Typescript project presentation during Hackweek 2019&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After considering the presentation, we felt Typescript was a strong fit for Sentry because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Several classes of bugs could be detected — and eliminated — during compilation.&lt;/li&gt;
&lt;li&gt;We could improve the developer experience through editor integrations such as auto-completion, faster code navigation, and inline compiler feedback.&lt;/li&gt;
&lt;li&gt;We could reduce the need for API documentation, as type annotations help produce self-describing code.&lt;/li&gt;
&lt;li&gt;TypeScript has an active community with a clear and maintained development &lt;a href="https://github.com/Microsoft/TypeScript/wiki/Roadmap" rel="noopener noreferrer"&gt;roadmap&lt;/a&gt; in addition to rapid &lt;a href="https://github.com/microsoft/TypeScript/releases" rel="noopener noreferrer"&gt;releases&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Many of the libraries we use (including React) already have &lt;a href="https://github.com/borisyankov/DefinitelyTyped" rel="noopener noreferrer"&gt;type definitions&lt;/a&gt; available.&lt;/li&gt;
&lt;li&gt;TypeScript can be adopted incrementally. That meant we can start writing new code with TypeScript and incrementally convert over time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There were some potential drawbacks of adopting TypeScript, though:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It’s a large time investment. Our frontend code is non-trivial in scope, so it would take significant effort to convert it. That complexity meant additional build time.&lt;/li&gt;
&lt;li&gt;We would need to educate the frontend team in TypeScript and support them as they learned.&lt;/li&gt;
&lt;li&gt;TypeScript and JavaScript would need to coexist in the code base for a significant period of time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Maturing the prototype
&lt;/h2&gt;

&lt;p&gt;Shortly after Hackweek, excitement was high and a more formal proposal was brought to our Frontend Technical Steering Committee (TSC). This group meets every two weeks to guide our frontend architecture. While TypeScript wasn’t among the “winning” projects for Hackweek, we were confident that it would be a worthwhile investment that would ultimately pay off in the long run.&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%2F1y5ctrzjg4ro3vctftbw.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%2F1y5ctrzjg4ro3vctftbw.jpeg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Sentry’s first Typescript Pull Request:&lt;/em&gt; &lt;a href="https://github.com/getsentry/sentry/pull/13786" rel="noopener noreferrer"&gt;https://github.com/getsentry/sentry/pull/13786&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Overall strategy
&lt;/h2&gt;

&lt;p&gt;We broke our high-level strategy into several phases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Educate&lt;/strong&gt;. In this phase, we needed to let people know that TypeScript was coming, and provide the right learning resources to help folks onboard.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;New code in TypeScript&lt;/strong&gt;. In this phase, we needed to have all new development being done in TypeScript. If we continued to create new JavaScript, we would never finish the conversion phase.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Conversion&lt;/strong&gt;. In this phase, all new work would be done in TypeScript, giving us a finite number of files to convert. Then it is “just work”™️.&lt;br&gt;
Our most controversial decision was agreeing to not undergo any other major refactors until the code base was converted 100% to TypeScript. This meant we would not take on other quality-of-life improvements — things like upgrading our state-management library or introducing React hooks — until the TypeScript conversion was complete.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Educating the team
&lt;/h2&gt;

&lt;p&gt;Early on, we recognized that the broader development team at Sentry would need additional resources and materials to learn TypeScript. To help folks who were new to TypeScript, we shared a list of introductory articles and resources for configuring various editors.&lt;/p&gt;

&lt;p&gt;Additionally, members of the TSC took the time to review code and help educate those folks eager to learn TypeScript. Having this support system in place helped create more TypeScript “believers” who would, over time, write new code in TypeScript.&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%2Fjd4yhtflwop7y4nouiqd.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%2Fjd4yhtflwop7y4nouiqd.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Example of a TypeScript code review.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&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%2Foakbsa0zh1w6aj47p0r7.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%2Foakbsa0zh1w6aj47p0r7.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Slack shoutout for TypeScript conversion.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Taking root in green fields
&lt;/h2&gt;

&lt;p&gt;While we were educating the broader team, folks who were keen on TypeScript not only began building out their new feature work in TypeScript, but also found opportunities to convert files which overlapped with new features. This approach let us build up our type definitions and gain more experience writing Typescript in lower-risk parts of the product that were not exposed to customers.&lt;/p&gt;

&lt;p&gt;As the broader team gained more experience and found value in what TypeScript provides, they naturally stopped creating more JavaScript. While we never used tooling to stop people from creating new JavaScript, our education efforts and social agreements helped prevent new JavaScript from being created.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work out from the core — and in from the leaves
&lt;/h2&gt;

&lt;p&gt;Once TypeScript had a firm foothold, we needed a strategy to work through the 1,100+ files that needed conversion. Here, we audited our imports, ordering them by how frequently each module was imported. We used this list to prioritize which modules were converted first. By converting frequently used modules, we would be able compound our gains as we converted files.&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%2F1b2bs41dfd353up4gxxc.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%2F1b2bs41dfd353up4gxxc.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The list we used to prioritize and assign conversion work based on import frequency.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This approach worked well in the beginning, as some modules have dramatically more imports than others. But because most of our modules have fewer than 10 imports, we quickly plateaued. Our next approach was starting from “leaf node” modules that are imported in one place. Converting these files enabled us to accumulate progress more quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  All we have to do now is convert 1,100+ files…
&lt;/h2&gt;

&lt;p&gt;Like many software projects, our initial rollout plans were overly ambitious. We started off by retroactively calculating a timeline where we completed within 2019. With approximately 15 weeks before the end of the year, that meant would need to convert approximately 74 files per week. This assumed that we would not accumulate any additional JavaScript files (we did) and that we could sustain that effort (we didn’t). After eight weeks, we checked in on our progress.&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%2Finhw4g2erp7b36ohvsuv.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%2Finhw4g2erp7b36ohvsuv.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Despite promising progress during our first 8 weeks, we knew we weren’t going to finish in 2019.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It was obvious that we were not going to complete it in 2019. Given projections from the current effort, a more likely completion date would be mid-2020.&lt;/p&gt;

&lt;p&gt;During the fall and winter of 2019, progress was slow. People were focusing on meeting product goals and didn’t have as much time to devote to TypeScript conversion. In February of 2020, we reached equilibrium. We were no longer making new JavaScript and our backlog of conversion work became fixed.&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%2Fgl79ewou9ek6g1seoh3z.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%2Fgl79ewou9ek6g1seoh3z.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Our conversion progress over time. Note there are several lulls as well as periods of renewed activity.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Challenges encountered
&lt;/h2&gt;

&lt;p&gt;Although the introduction of TypeScript was definitely a game-changer, we also faced a few challenges during the conversion process. Most of these were due to interoperability issues between TypeScript and React:&lt;/p&gt;

&lt;p&gt;1.&lt;strong&gt;Default Props&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When using &lt;code&gt;defaultProps&lt;/code&gt; on classes, TypeScript is able to correctly infer that the props are not required when using the component, but when using Higher Order Components, the types for &lt;code&gt;defaultProps&lt;/code&gt; generally don’t work, and the previously optional properties would become required.&lt;/p&gt;

&lt;p&gt;An example of how &lt;code&gt;defaultProps&lt;/code&gt; interacts poorly with Higher Order Components is:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;defaultProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;statsPeriod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_STREAM_GROUP_STATS_PERIOD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;canSelect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;withChart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;useFilteredStats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GlobalSelection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;organization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Organization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;displayReprocessingLayout&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;hasGuideAnchor&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;memberList&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nx"&gt;onMarkReviewed&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;itemIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;showInboxTime&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;defaultProps&lt;/span&gt;

&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{...};&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StreamGroup&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;defaultProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;defaultProps&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="nf"&gt;withGlobalSelection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;withOrganization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;StreamGroup&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normally, TypeScript would be able to use the &lt;code&gt;defaultProps&lt;/code&gt; attribute of our class component to infer that those properties are not required. However, when wrapped in a Higher Order Component, TypeScript displays the following errors:&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%2Fe5xui5azt6bttuu0v5po.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%2Fe5xui5azt6bttuu0v5po.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here our solution was to use &lt;code&gt;Partial&lt;/code&gt; on the &lt;code&gt;defaultProps&lt;/code&gt; and rely on React to fill in the default values.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;defaultProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;statsPeriod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_STREAM_GROUP_STATS_PERIOD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;canSelect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;withChart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;useFilteredStats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GlobalSelection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;organization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Organization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;displayReprocessingLayout&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;hasGuideAnchor&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;memberList&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
      &lt;span class="nx"&gt;onMarkReviewed&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;itemIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;showInboxTime&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;defaultProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{...};&lt;/span&gt;

    &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StreamGroup&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;defaultProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;defaultProps&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="nf"&gt;withGlobalSelection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;withOrganization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;StreamGroup&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 find a more complete implementation of &lt;a href="https://github.com/getsentry/sentry/blob/master/src/sentry/static/sentry/app/components/stream/group.tsx" rel="noopener noreferrer"&gt;this approach here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;2.&lt;strong&gt;Libraries adding incorrect types&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One downside to relying on the type definitions in DefinitelyTyped is that occasionally the library types are not written by the maintainers. Instead, community users contribute types, and because of that some types are missing or incorrectly defined. We encountered this with the versions of &lt;a href="https://echarts.apache.org/en/index.html" rel="noopener noreferrer"&gt;ECharts&lt;/a&gt; and &lt;a href="https://github.com/reflux/refluxjs" rel="noopener noreferrer"&gt;Reflux&lt;/a&gt; we were using. Our solution here was to add additional type definitions in our code.&lt;/p&gt;

&lt;p&gt;3.&lt;strong&gt;React.forwardRef is not compatible with generics&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using generic types with &lt;code&gt;React.forwardRef&lt;/code&gt; is not directly possible, as it requires concrete types. In more detail, the &lt;code&gt;forwardRef&lt;/code&gt; function has only one parameter named &lt;code&gt;render&lt;/code&gt;. The type of this parameter is &lt;code&gt;ForwardRefRenderFunction&lt;/code&gt;, which is not a generic function declaration, so higher order function type inference cannot propagate free type parameters on to the calling function &lt;code&gt;React.forwardRef&lt;/code&gt;.  We had to make &lt;a href="https://github.com/getsentry/sentry/pull/23766/files" rel="noopener noreferrer"&gt;compromises and use “any” when this situation arose&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sustaining motivation and energy
&lt;/h2&gt;

&lt;p&gt;Toward the end of the conversion, many contributors were feeling the burning toil this project was having.&lt;/p&gt;

&lt;p&gt;In the summer of 2020 – a full year after this project began – we crossed the 70% threshold. This revitalized folks, as we knew the end was near. We were able to sustain that energy and focus though the summer and fall by using part of our TSC meeting as a check in and collecting “conversion pledges” for the next meeting. This introduced a light-hearted social game that helped us stay focused.&lt;/p&gt;

&lt;p&gt;In addition, our fantastic tools team introduced a slackbot that would allow us to track progress on demand. Seeing the number go up every day was a big motivator in the final stages, so much so it’s something we’ll likely to use again. You can find the early versions of that bot &lt;a href="https://github.com/getsentry/TypeScript-slack-bot" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The end, finally
&lt;/h2&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%2Fr0fc1zd8c5nr34x9po9e.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%2Fr0fc1zd8c5nr34x9po9e.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After 18 months of migrating our frontend code base to TypeScript, the day everyone at Sentry had been working toward had finally arrived. When we started on our TypeScript journey, we had 1,100+ files to convert. Now, we have over 1,915 Typescript files. It’s worth mentioning that at no time was a GitHub check added to block new JavaScript files. After developers saw the benefits that TypeScript would bring, writing new code in TypeScript was an organic choice.&lt;/p&gt;

&lt;p&gt;With TypeScript, we now have an extra layer of protection in our code, which means we’re able to ship with greater confidence, higher productivity, and most importantly, fewer bugs. Some of our newer frontend developers have never seen a production incident caused by a frontend change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking back
&lt;/h2&gt;

&lt;p&gt;Like everything in life, we also learned a few things along this journey.&lt;/p&gt;

&lt;p&gt;1.&lt;strong&gt;Incremental conversion is key&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Our strategy to gradually migrate our files to TypeScript worked out well. We were able to balance converting our code to TypeScript, without delaying important product work. It’s important to highlight that from the beginning, we were not in a hurry to achieve our goal, but instead we wanted to proceed carefully and do a great job.&lt;/p&gt;

&lt;p&gt;2.&lt;strong&gt;Stay current with TypeScript releases&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;During our conversion process, several new releases of TypeScript came out. Each one helped us refine our types further with new features like optional chaining, nullish coalesce, named tuples, and more. While upgrading did take additional effort, the benefits were well worth it. It’s why we recommend staying as current as you can with TypeScript releases.&lt;/p&gt;

&lt;p&gt;3.&lt;strong&gt;Gradually build complex types&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At the beginning of the migration, it was impossible to know the correct type of everything. After all, Sentry possesses a large code base, and not everyone is familiar with all parts of the application. Which meant we had to build our more complex types incrementally. As we were converting files, we became more familiar with their types and as we converted related files, we were able to better identify whether the types we had previously defined were updated with the new insights.&lt;/p&gt;

&lt;p&gt;4.&lt;strong&gt;Use TODO Comments to note future work&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Sentry, we generally use TODO comments in the code to help us track something we need to review later. This approach proved to be very useful during our migration to TypeScript. When we encountered an unclear type, or problematic component we would leave a &lt;code&gt;TODO(ts)&lt;/code&gt; for later review. We are now incrementally reviewing the TODO list and further refining and improving our types.&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;Frame&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="s1"&gt;app/types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// TODO(ts): define correct stack trace type&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getRelevantFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stacktrace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Frame&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;stacktrace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasSystemFrames&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="nx"&gt;stacktrace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frames&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;stacktrace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stacktrace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stacktrace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frames&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&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="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inApp&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="nx"&gt;frame&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="c1"&gt;// this should not happen&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;stacktrace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frames&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;stacktrace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&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;getRelevantFrame&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Moving forward
&lt;/h2&gt;

&lt;p&gt;Migrating to TypeScript was just the beginning. The frontend team at Sentry will continue to gradually improve types, making sure they are correct as possible, including the removal of all React PropTypes.&lt;/p&gt;

&lt;p&gt;We’re also seriously considering introducing end-to-end type safety, so that a backend engineer can make changes in the API without unknowing breaking clients, and frontend engineers can be confident in the data that will be coming back from the server.&lt;/p&gt;

&lt;p&gt;This important achievement would not have been possible without the patience, persistence, attention to detail, passion and hard work of everyone involved. A big thank you to all the Sentaurs who contributed to this enormous effort.&lt;/p&gt;

&lt;p&gt;Eager for a challenging project like this one? Then join us at Sentry. &lt;a href="https://sentry.io/careers/" rel="noopener noreferrer"&gt;We are hiring!&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>react</category>
      <category>javascript</category>
      <category>sentry</category>
    </item>
  </channel>
</rss>
