{"id":75649,"date":"2019-02-28T12:28:13","date_gmt":"2019-02-28T20:28:13","guid":{"rendered":"https:\/\/cloudblogs.microsoft.com\/opensource\/?p=75649"},"modified":"2025-06-29T08:28:24","modified_gmt":"2025-06-29T15:28:24","slug":"tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app","status":"publish","type":"post","link":"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/","title":{"rendered":"Tutorial: Adding distributed tracing instrumentation to a browser JavaScript app"},"content":{"rendered":"\n<p>In recent years, many developers have discovered the power of distributed tracing for debugging regressions and performance issues in their backend systems, especially for those of us with complex microservices architectures. But in many organizations, our most complex code may not be server-side code \u2014 it\u2019s just as likely to be running client-side in a browser or mobile app.<\/p>\n\n\n\n<p>While it\u2019s still less common to see tracing instrumentation in client-side code, there\u2019s no reason we can\u2019t use it there. The high resolution, depth-first debugging capabilities tracing offers are useful regardless of where our code is running \u2014 and tracing in the client can give us a rich view of how users are interacting with our app. This article walks through a practical, incremental approach to using tracing-style instrumentation in client-side JavaScript apps.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"prerequisites\">Prerequisites<\/h2>\n\n\n\n<p>To get started, you\u2019ll need:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A web application that uses client-side javascript. I will show code from a React app using ES6 with Babel, but any app that includes more than a few lines of client-side javascript should work.<\/li>\n\n\n\n<li>A distributed tracing tool to view the data from your instrumentation. I\u2019ll use\u00a0<a href=\"http:\/\/honeycomb.io\/\">io<\/a>, but most modern distributed tracing tools, including open source options\u00a0<a href=\"https:\/\/zipkin.io\/\">Zipkin<\/a>\u00a0&amp;\u00a0<a href=\"https:\/\/www.jaegertracing.io\/\">Jaeger<\/a>, and other popular vendor tools like\u00a0<a href=\"https:\/\/lightstep.com\/\">LightStep<\/a>\u00a0and\u00a0<a href=\"https:\/\/aws.amazon.com\/xray\/\">AWS X-Ray<\/a>, work too.<\/li>\n<\/ul>\n\n\n\n<p>Other things that may be handy:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Existing client-side instrumentation. If you are already sending page load timing metrics like navigation timing data to your monitoring &amp; observability tooling, you may want to add these numbers as key\/value pairs to your traces.<\/li>\n\n\n\n<li>A proxy endpoint in your own infrastructure. Some tracing tools will allow you to send traces directly to them from the browser, but proxying through your own infrastructure may be helpful so you can enrich your events with backend data (like a user ID, IP address, or team ID) without exposing that data to the client.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-does-tracing-data-look-like\">What does tracing data look like?<\/h2>\n\n\n\n<p>As you start instrumenting, it\u2019s useful to have a mental picture of the shape of the data you will send. So what goes into a typical trace? A trace is composed of many spans (also called events). Each span represents one unit of work for a service, application, or piece of code. For example, a single database query or one HTTP request to a third party service might each be represented as a span. Spans can have parent-child relationships to each other, so you may see a trace representing an HTTP request to an app server; that trace may include a child span representing an HTTP request to an internal service, and then that child span might have many child spans of its own representing different database queries it triggered.<\/p>\n\n\n\n<p>Here are some common types of fields you\u2019ll see that help describe those spans:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>span ID<\/strong>: an arbitrary unique ID used to identify this particular unit of work<\/li>\n\n\n\n<li><strong>trace ID<\/strong>: an arbitrary unique ID describing the trace this span is a part of. In a typical backend application, a trace might represent a single user HTTP request to a web application. In the browser, it\u2019s more likely to represent a larger unit of work, like a page view.<\/li>\n\n\n\n<li><strong>parent span ID<\/strong>: this field is used to draw a causal relationship between two spans. For example, if your web application makes an http request to a backend service that then triggers a database query, the span for the database query would use the span ID of the backend service span as its parent span ID.<\/li>\n\n\n\n<li><strong>name<\/strong>: the particular type of work this span represents, e.g. \u201crender json,\u201d \u201cselect,\u201d \u201csave to S3,\u201d etc.<\/li>\n\n\n\n<li><strong>service name<\/strong>: the area of the codebase this span came from. In the browser, this might help you identify a particular package or area of your code (\u201crouter,\u201d \u201credux,\u201d \u201casync renderer\u201d).<\/li>\n\n\n\n<li><strong>duration<\/strong>: the amount of time this particular unit of work took, in milliseconds.<\/li>\n<\/ul>\n\n\n\n<p>Different distributed Tracing APIs use different field names, so check the docs of the tool or API you are using to find the right ones.&nbsp;<a href=\"https:\/\/opencensus.io\/tracing\/\">OpenCensus<\/a>&nbsp;and&nbsp;<a href=\"https:\/\/opentracing.io\/docs\/overview\/\">OpenTracing<\/a>&nbsp;are great vendor-agnostic starting points if you aren\u2019t sure which tracing tool you expect to use over the long term.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"sending-your-first-span-page-load\">Sending your first span: page load<\/h2>\n\n\n\n<p>In backend applications, the root span \u2014 the span that represents the bounds of the whole trace \u2014 often maps to a single user request to your system. In the browser, it\u2019s less clear what the root span should map to. A single request? A page view? A session? The single request option is too narrow for most use cases \u2014 it might help you spot a performance bottleneck, but it won\u2019t do very much to help you understand how your users are experiencing your site. The page view and session options both will allow you to see&nbsp;<em>sequences&nbsp;<\/em>of user interactions instead of single atomic interactions. Choosing one or the other depends somewhat on the constraints of the tracing tool you are using. If navigating long time spans, e.g. minutes or hours, is difficult, a single page load may be better. If you have a single-page app, it may not make much difference which you choose, since users are likely to spend a whole session on a single page load. Some tools, including Honeycomb, allow you to choose which field to use as the trace ID field, so in that case, you may be able to send both and toggle between page-level traces and session-level traces in the UI.<\/p>\n\n\n\n<p>In this case, we\u2019ve decided each trace will represent a single page load. We need a few pieces of code to send our first span (the root span) and thereby start a trace.<\/p>\n\n\n\n<p>First, you\u2019ll want a reusable function to handle the mechanics of sending spans. This is ours:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ Generates random string ID like \"2147483648\" for each span in a trace.\nconst newSpanID = () => Math.floor(Math.random() * 2 ** 31).toString();\nconst traceID = newSpanID();\n\nexport default class Tracing {\n\n sendSpan(spanName, metadata, duration, isRootSpan) {\n   const params = {\n     name: spanName,\n     service_name: metadata.serviceName,\n     duration_ms: duration,\n     \"trace.trace_id\": traceID,\n    \n     \/\/ If no parent ID is passed, just attach to the root span\n     \"trace.parent_id\": isRootSpan ? null : (metadata.parentID || traceID),\n     \"trace.span_id\": isRootSpan ? traceID : newSpanID(),\n    \n     \/\/ Send the current state of all feature flags with each span\n     ...flags,\n    \n     \/\/ Send the rest of our fields\n     ...metadata,\n   };\n\n   \/\/ Use the Beacon API to send spans to our proxy endpoint, so we don't\n   \/\/ have to worry about missing spans if the user navigates while sending.\n   navigator.sendBeacon(`\/create_span\/`, JSON.stringify(params));\n }\n}\n<\/pre><\/div>\n\n\n<p>Then, you\u2019ll need to add code to create your span on page load. It not only adds an on-load listener to create and send the event, but also handles capturing some client metadata and performance data about the current page.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ Start a trace every time someone loads a Honeycomb page in the browser,\n\/\/ and capture perf stats about the current page load.\n\/\/ Assumes the presence of a `window.performance.timing` object\nconst pageLoadMetadata = function() {\n const nt = window.performance.timing;\n const hasPerfTimeline = !!window.performance.getEntriesByType;\n totalDurationMS = nt.loadEventEnd - nt.connectStart;\n\n const metadata = {\n   \/\/ Chrome-only (for now) information on internet connection type (4g, wifi, etc.)\n   \/\/ https:\/\/developers.google.com\/web\/updates\/2017\/10\/nic62\n   connection_type: navigator.connection && navigator.connection.type,\n   connection_type_effective: navigator.connection && navigator.connection.effectiveType,\n   connection_rtt: navigator.connection && navigator.connection.rtt,\n\n   \/\/ Navigation timings, transformed from timestamps into deltas (shortened)\n   timing_unload_ms: nt.unloadEnd - nt.navigationStart,\n   timing_dns_end_ms: nt.domainLookupEnd - nt.navigationStart,\n   timing_ssl_end_ms: nt.connectEnd - nt.navigationStart,\n   timing_response_end_ms: nt.responseEnd - nt.navigationStart,\n   timing_dom_interactive_ms: nt.domInteractive - nt.navigationStart,\n   timing_dom_complete_ms: nt.domComplete - nt.navigationStart,\n   timing_dom_loaded_ms: nt.loadEventEnd - nt.navigationStart,\n   timing_ms_first_paint: nt.msFirstPaint - nt.navigationStart, \/\/ Nonstandard IE\/Edge-only first pain\n\n   \/\/ Entire page load duration\n   timing_total_duration_ms: totalDurationMS,\n  \n   \/\/ Client properties\n   user_agent: window.navigator.userAgent,\n   window_height: window.innerHeight,\n   window_width: window.innerWidth,\n   screen_height: window.screen && window.screen.height,\n   screen_width: window.screen && window.screen.width,\n };\n\n \/\/ PerformancePaintTiming data (Chrome only for now)\n if (hasPerfTimeline) {\n   let paints = window.performance.getEntriesByType(\"paint\");\n\n   \/\/ Loop through array of two PerformancePaintTimings and send both\n   _.each(paints, paint => {\n     if (paint.name === \"first-paint\") {\n       metadata.timing_first_paint_ms = paint.startTime;\n     } else if (paint.name === \"first-contentful-paint\") {\n       metadata.timing_first_contentful_paint_ms = paint.startTime;\n     }\n   });\n }\n\n \/\/ Redirect Count (inconsistent browser support)\n metadata.redirect_count =\n   window.performance.navigation && window.performance.navigation.redirectCount;\n\n return metadata;\n};\n\nwindow.addEventListener(\"load\", () => {\n   _userEvents.pageLoad(pageLoadMetadata(), totalDurationMS);\n});\n<\/pre><\/div>\n\n\n<p>The duration field on this span presents an interesting dilemma. Do we want it to represent&nbsp;<em>just<\/em>&nbsp;the work of initially loading the page, or should it represent the entire time the user spent&nbsp;<em>viewing<\/em>&nbsp;the page? In this case, I chose the former, because initial page loading is something I\u2019m very interested in quantifying. I will also send a final span when the browser fires its \u201cunload\u201d event to help us capture the time spent viewing the page. However, if your tracing tool doesn\u2019t have good support for displaying asynchronous spans \u2014 spans that have a start time or duration that push them outside the bounds of the root span \u2014 you may want to instead send this page load span on unload and have its duration represent the entire time spent viewing the page.<\/p>\n\n\n<figure class=\"wp-block-image aligncenter\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-1-1024x652.webp\" alt=\"Tracing screenshot\" class=\"wp-image-75712 webp-format\" srcset=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-1.webp 1148w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-1-300x191.webp 300w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-1-1024x652.webp 1024w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-1-768x489.webp 768w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-1-330x210.webp 330w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-1-800x509.webp 800w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-1-400x255.webp 400w\" data-orig-src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-1.png\" data-orig-srcset=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-1.png 1148w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-1-300x191.png 300w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-1-1024x652.png 1024w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-1-768x489.png 768w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-1-330x210.png 330w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-1-800x509.png 800w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-1-400x255.png 400w\"><\/figure>\n\n\n\n<p>This gets us one span, which is both an \u201cevent\u201d and a \u201ctrace!\u201d It\u2019s not very interesting, though. While it might tell us whether a particular page load was fast or slow, it doesn\u2019t help us understand much about the user\u2019s experience on that page. So let\u2019s add more spans!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"tracking-browser-events\">Tracking browser events<\/h2>\n\n\n\n<p>What else can we add to this trace view? One option that will help us get a higher-fidelity picture of our user experience is adding a span each time a user triggers an interesting event like a mouse click or a tap. If you already send custom events to a product analytics tool, these are often the exact kinds of events you want to capture. For example, this app sends product analytics events to both Amplitude and Honeycomb, so the easiest thing to do is have our analytics code also call our sendSpan function defined above. In our case, that gets us lots of events:<\/p>\n\n\n<figure class=\"wp-block-image aligncenter\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-2-1024x652.webp\" alt=\"Tracing screenshot\" class=\"wp-image-75715 webp-format\" srcset=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-2.webp 1148w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-2-300x191.webp 300w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-2-1024x652.webp 1024w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-2-768x489.webp 768w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-2-330x210.webp 330w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-2-800x509.webp 800w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-2-400x255.webp 400w\" data-orig-src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-2.png\" data-orig-srcset=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-2.png 1148w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-2-300x191.png 300w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-2-1024x652.png 1024w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-2-768x489.png 768w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-2-330x210.png 330w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-2-800x509.png 800w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-2-400x255.png 400w\"><\/figure>\n\n\n\n<p>We\u2019ve included a default duration of 100ms to make sure all of these show on the waterfall view, but in an ideal world we would use the duration to track the time it took the browser to bubble the event, call the event handler, and render any resulting changes.<\/p>\n\n\n\n<p>If you don\u2019t have existing product analytics instrumentation to hook into, you can always start by setting up event listeners for interesting events like clicks. Here\u2019s a simple example of what that code would look like \u2014 in a real app you might want to add some logic to filter for only the most interesting click targets.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nconst t = new Tracing();\n\n\/\/ Adds a span to our traces for each user click\ndocument.addEventListener(\"click\", e => {\n const start = window.performance.now();\n\n \/\/ Wrap in a setTimeout so the duration includes the running time of the\n \/\/ relevant event handler & related page updates.\n setTimeout(() => {\n   const duration = window.performance.now() - start;\n\n   t.sendSpan(\"click\", {\n     clickTargetClass: e.target.className,\n     clickTargetID: e.target.id,\n   }, duration);\n }, 0);\n\n\/\/ Use click capture so we can record the start time closer to the user's action\n}, true);\n<\/pre><\/div>\n\n\n<p>Some of our most interesting cases to track will be cases where a user interaction \u2014 like clicking a button \u2014 triggers an http request \u2014 like submitting a form via xhr. If possible, it\u2019s also good to track xhr requests, plus any response process and DOM mutation that result, as spans too. In our React app, we can use our requests module and life cycle callbacks like componentDidUpdate to do this, but the exact patterns will vary from app to app.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"tracking-errors\">Tracking errors<\/h2>\n\n\n\n<p>Even though our team uses a dedicated error monitoring tool (something like Sentry, Bugsnag, Airbrake, etc.) with this app to give us a more convenient team workflow for finding and fixing errors, we still want to see errors in our traces too. This will help us understand if a user\u2019s experience might be unsuccessful or slow because of a preventable JavaScript error. That way if a user loads a page and then immediately leaves, we can guess if they left because something went wrong, or because they didn\u2019t find what they were looking for.<\/p>\n\n\n<figure class=\"wp-block-image aligncenter\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-3-1024x652.webp\" alt=\"Tracing screenshot\" class=\"wp-image-75718 webp-format\" srcset=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-3.webp 1148w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-3-300x191.webp 300w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-3-1024x652.webp 1024w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-3-768x489.webp 768w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-3-330x210.webp 330w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-3-800x509.webp 800w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-3-400x255.webp 400w\" data-orig-src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-3.png\" data-orig-srcset=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-3.png 1148w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-3-300x191.png 300w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-3-1024x652.png 1024w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-3-768x489.png 768w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-3-330x210.png 330w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-3-800x509.png 800w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-3-400x255.png 400w\"><\/figure>\n\n\n\n<p>If you do use a dedicated error monitoring tool, consider adding a few custom fields to your error reports: your tracing spanID and a link to your tracing tool that will allow you to query for this particular span. That will let you easily jump from your error monitoring tool back to your tracing tool if you find you want to better understand the context around a particular error.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nconst t = new Tracing();\n\n\/\/ Sends a span each time an error happens in the browser\nwindow.onerror = (message, source, lineno, colno, error) => {\n const start = window.performance.now();\n const spanID = newSpanID();\n\n \/\/ Send a unique ID to our error monitoring service AND add a reference back\n \/\/ to this span, so we can cross-link easily between tools.\n recordError(error, {\n   honeycombSpanID: spanID,\n   viewHoneycombTraceURL: `https:\/\/example.com\/link\/to\/our\/trace\/${spanID}`\n })\n\n t.sendSpan(\"error\", {\n   spanID: spanID,\n   name: error.name,\n   \"error.message\": message,\n   \"error.source\": source,\n   \"error.lineno\": lineno,\n   \"error:colno\": colno,\n }, window.performance.now() - start);\n};\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"tracking-navigations\">Tracking navigations<\/h2>\n\n\n\n<p>Finally, our React app includes some single-page-app-style behavior, where large portions of the page will change without doing a full browser navigation. These kinds of page changes are great things to track in our traces, because they help us understand how users are navigating around our app.<\/p>\n\n\n\n<p>If you\u2019re using a client-side routing solution that lets you fire a callback every time the user performs a navigation, that\u2019s a great place to send a span. for the rest of us, we\u2019ll need to shim calls to history-changing functions like pushState and replaceState to get them to do our bidding. Here\u2019s an example:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nconst t = new Tracing();\n\n\/\/ Shim the browser's native pushState function\nconst pushState = history.pushState;\n\n\/\/ Send a span for each pushState call\nhistory.pushState = function (stateObj, title, location) {\n const lastPage = window.location.pathname;\n\n \/\/ Call through to the old pushState so the location\/history actually changes\n pushState.apply(history, arguments);\n\n \/\/ Wrap in this in a setTimeout so the duration includes the running time of\n \/\/ related page updates.\n setTimeout(() => {\n   t.sendSpan(\"pushState\", {\n     lastPage,\n     newPage: location,\n   }, duration);\n\n }, 0);\n};\n<\/pre><\/div>\n\n\n<p>Between events, navigations, and errors, we have a record of all the major things a user did while navigating our page. Not bad!<\/p>\n\n\n<figure class=\"wp-block-image aligncenter\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-4-1024x652.webp\" alt=\"Tracing screenshot\" class=\"wp-image-75721 webp-format\" srcset=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-4.webp 1148w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-4-300x191.webp 300w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-4-1024x652.webp 1024w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-4-768x489.webp 768w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-4-330x210.webp 330w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-4-800x509.webp 800w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-4-400x255.webp 400w\" data-orig-src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-4.png\" data-orig-srcset=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-4.png 1148w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-4-300x191.png 300w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-4-1024x652.png 1024w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-4-768x489.png 768w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-4-330x210.png 330w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-4-800x509.png 800w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_image-4-400x255.png 400w\"><\/figure>\n\n\n\n<p>This is a great starting point that will give us a high-level overview of users\u2019 interactions with each page of our app. As we find other significant interaction categories, we can add a span for each one.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"privacy\">Privacy<\/h2>\n\n\n\n<p>While sending client-side telemetry like performance metrics, errors, and product analytics to your application server or third-party tools has been a standard practice in the industry for many years, tracing data usually presents a higher-fidelity view of user behavior than was available in the prior generation of tools. Tracing-style tools also often have better support for high cardinality querying, meaning it can be easier to associate a particular set of behaviors with an individual IP addresses or person. This higher-fidelity view into users\u2019 activity brings with it new potential user privacy concerns.<\/p>\n\n\n\n<p>Some suggestions:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Consider whether a particular piece of metadata will truly be helpful for debugging or understanding user experience before you include it in your traces. Is there a less-revealing field value that could do the same work?<\/li>\n\n\n\n<li>Avoid sending extra user &amp; application metadata to the client\u00a0<em>just<\/em>\u00a0to be included in instrumentation. Instead, pipe tracing data through your own infrastructure to enrich it there, rather than exposing that data client-side where third-party scripts may also have access to it.<\/li>\n\n\n\n<li>Check the data retention policies &amp; data access practices of your third-party vendors, if you are sending your tracing data outside your own systems. Do they support sending and storing PII? In what circumstances are they able to access your data, and for how long?<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"learn-more\">Learn More<\/h2>\n\n\n\n<p>Sending distributed tracing-style instrumentation from the browser is still a relatively uncommon practice, and we are still finding new patterns and figuring out the best practices as a community. If you\u2019re interested in seeing more approaches to browser tracing and client-side instrumentation, check out:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/github.com\/opentracing\/opentracing-javascript\">opentracing-javascript<\/a>: an Open Tracing API implementation in javascript, suitable for both Node.js and browser applications<\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/openzipkin\/zipkin-js\">zipkin-js<\/a>: a library to do Zipkin-style tracing instrumentation in both Node.js and browser applications<\/li>\n\n\n\n<li>an older\u00a0<a href=\"https:\/\/www.honeycomb.io\/blog\/instrumenting-browser-page-loads-at-honeycomb\/\">blog post on our prior approach to browser instrumentation<\/a>\u00a0at Honeycomb, pre-tracing.<\/li>\n<\/ul>\n\n\n\n<p>If you have questions or suggestions from your own client-side tracing, let us know in the comments! Happy tracing.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"follow-emily-eanakashima\"><em>Follow Emily&nbsp;<a href=\"https:\/\/twitter.com\/eanakashima\">@eanakashima<\/a><\/em><\/h3>\n","protected":false},"excerpt":{"rendered":"<p>In recent years, many developers have discovered the power of distributed tracing for debugging regressions and performance issues in their backend systems, especially for those of us with complex microservices architectures.<\/p>\n","protected":false},"author":5562,"featured_media":75742,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"msxcm_post_with_no_image":false,"ep_exclude_from_search":false,"_classifai_error":"","_classifai_text_to_speech_error":"","footnotes":""},"post_tag":[2271],"content-type":[340],"topic":[2240,2241],"programming-languages":[],"coauthors":[460],"class_list":["post-75649","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","tag-community-partners","content-type-tutorials-and-demos","topic-application-development","topic-cloud","review-flag-1593580428-734","review-flag-1593580415-931","review-flag-1593580771-946","review-flag-2-1593580437-411","review-flag-alway-1593580310-39","review-flag-new-1593580248-669"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.2 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Tutorial: Adding distributed tracing instrumentation to a browser JavaScript app | Microsoft Open Source Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Tutorial: Adding distributed tracing instrumentation to a browser JavaScript app | Microsoft Open Source Blog\" \/>\n<meta property=\"og:description\" content=\"In recent years, many developers have discovered the power of distributed tracing for debugging regressions and performance issues in their backend systems, especially for those of us with complex microservices architectures.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/\" \/>\n<meta property=\"og:site_name\" content=\"Microsoft Open Source Blog\" \/>\n<meta property=\"article:published_time\" content=\"2019-02-28T20:28:13+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-06-29T15:28:24+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_featured-image.png\" \/>\n\t<meta property=\"og:image:width\" content=\"700\" \/>\n\t<meta property=\"og:image:height\" content=\"200\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Emily Nakashima\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing-image_twitter.png\" \/>\n<meta name=\"twitter:creator\" content=\"@OpenAtMicrosoft\" \/>\n<meta name=\"twitter:site\" content=\"@OpenAtMicrosoft\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Emily Nakashima\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"11 min read\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/\"},\"author\":[{\"@id\":\"https:\/\/opensource.microsoft.com\/blog\/author\/emily-nakashima\/\",\"@type\":\"Person\",\"@name\":\"Emily Nakashima\"}],\"headline\":\"Tutorial: Adding distributed tracing instrumentation to a browser JavaScript app\",\"datePublished\":\"2019-02-28T20:28:13+00:00\",\"dateModified\":\"2025-06-29T15:28:24+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/\"},\"wordCount\":2180,\"commentCount\":1,\"publisher\":{\"@id\":\"https:\/\/opensource.microsoft.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_featured-image.png\",\"keywords\":[\"Community\/partners\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/\",\"url\":\"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/\",\"name\":\"Tutorial: Adding distributed tracing instrumentation to a browser JavaScript app | Microsoft Open Source Blog\",\"isPartOf\":{\"@id\":\"https:\/\/opensource.microsoft.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_featured-image.png\",\"datePublished\":\"2019-02-28T20:28:13+00:00\",\"dateModified\":\"2025-06-29T15:28:24+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/#primaryimage\",\"url\":\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_featured-image.png\",\"contentUrl\":\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_featured-image.png\",\"width\":700,\"height\":200,\"caption\":\"a screenshot of a cell phone\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/opensource.microsoft.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Tutorial: Adding distributed tracing instrumentation to a browser JavaScript app\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/opensource.microsoft.com\/blog\/#website\",\"url\":\"https:\/\/opensource.microsoft.com\/blog\/\",\"name\":\"Microsoft Open Source Blog\",\"description\":\"Open dialogue about openness at Microsoft \u2013 open source, standards, interoperability\",\"publisher\":{\"@id\":\"https:\/\/opensource.microsoft.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/opensource.microsoft.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/opensource.microsoft.com\/blog\/#organization\",\"name\":\"Microsoft Open Source Blog\",\"url\":\"https:\/\/opensource.microsoft.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/opensource.microsoft.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/08\/Microsoft-Logo.png\",\"contentUrl\":\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/08\/Microsoft-Logo.png\",\"width\":259,\"height\":194,\"caption\":\"Microsoft Open Source Blog\"},\"image\":{\"@id\":\"https:\/\/opensource.microsoft.com\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/x.com\/OpenAtMicrosoft\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Tutorial: Adding distributed tracing instrumentation to a browser JavaScript app | Microsoft Open Source Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/","og_locale":"en_US","og_type":"article","og_title":"Tutorial: Adding distributed tracing instrumentation to a browser JavaScript app | Microsoft Open Source Blog","og_description":"In recent years, many developers have discovered the power of distributed tracing for debugging regressions and performance issues in their backend systems, especially for those of us with complex microservices architectures.","og_url":"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/","og_site_name":"Microsoft Open Source Blog","article_published_time":"2019-02-28T20:28:13+00:00","article_modified_time":"2025-06-29T15:28:24+00:00","og_image":[{"width":700,"height":200,"url":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_featured-image.png","type":"image\/png"}],"author":"Emily Nakashima","twitter_card":"summary_large_image","twitter_image":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing-image_twitter.png","twitter_creator":"@OpenAtMicrosoft","twitter_site":"@OpenAtMicrosoft","twitter_misc":{"Written by":"Emily Nakashima","Est. reading time":"11 min read"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/#article","isPartOf":{"@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/"},"author":[{"@id":"https:\/\/opensource.microsoft.com\/blog\/author\/emily-nakashima\/","@type":"Person","@name":"Emily Nakashima"}],"headline":"Tutorial: Adding distributed tracing instrumentation to a browser JavaScript app","datePublished":"2019-02-28T20:28:13+00:00","dateModified":"2025-06-29T15:28:24+00:00","mainEntityOfPage":{"@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/"},"wordCount":2180,"commentCount":1,"publisher":{"@id":"https:\/\/opensource.microsoft.com\/blog\/#organization"},"image":{"@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/#primaryimage"},"thumbnailUrl":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_featured-image.png","keywords":["Community\/partners"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/","url":"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/","name":"Tutorial: Adding distributed tracing instrumentation to a browser JavaScript app | Microsoft Open Source Blog","isPartOf":{"@id":"https:\/\/opensource.microsoft.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/#primaryimage"},"image":{"@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/#primaryimage"},"thumbnailUrl":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_featured-image.png","datePublished":"2019-02-28T20:28:13+00:00","dateModified":"2025-06-29T15:28:24+00:00","breadcrumb":{"@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/#primaryimage","url":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_featured-image.png","contentUrl":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/02\/Tracing_featured-image.png","width":700,"height":200,"caption":"a screenshot of a cell phone"},{"@type":"BreadcrumbList","@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/02\/28\/tutorial-adding-distributed-tracing-instrumentation-to-browser-javascript-app\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/opensource.microsoft.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Tutorial: Adding distributed tracing instrumentation to a browser JavaScript app"}]},{"@type":"WebSite","@id":"https:\/\/opensource.microsoft.com\/blog\/#website","url":"https:\/\/opensource.microsoft.com\/blog\/","name":"Microsoft Open Source Blog","description":"Open dialogue about openness at Microsoft \u2013 open source, standards, interoperability","publisher":{"@id":"https:\/\/opensource.microsoft.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/opensource.microsoft.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/opensource.microsoft.com\/blog\/#organization","name":"Microsoft Open Source Blog","url":"https:\/\/opensource.microsoft.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/opensource.microsoft.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/08\/Microsoft-Logo.png","contentUrl":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/08\/Microsoft-Logo.png","width":259,"height":194,"caption":"Microsoft Open Source Blog"},"image":{"@id":"https:\/\/opensource.microsoft.com\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/OpenAtMicrosoft"]}]}},"msxcm_display_generated_audio":false,"msxcm_animated_featured_image":null,"distributor_meta":false,"distributor_terms":false,"distributor_media":false,"distributor_original_site_name":"Microsoft Open Source Blog","distributor_original_site_url":"https:\/\/opensource.microsoft.com\/blog","push-errors":false,"_links":{"self":[{"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/posts\/75649","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/users\/5562"}],"replies":[{"embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/comments?post=75649"}],"version-history":[{"count":1,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/posts\/75649\/revisions"}],"predecessor-version":[{"id":97817,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/posts\/75649\/revisions\/97817"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/media\/75742"}],"wp:attachment":[{"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/media?parent=75649"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/post_tag?post=75649"},{"taxonomy":"content-type","embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/content-type?post=75649"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/topic?post=75649"},{"taxonomy":"programming-languages","embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/programming-languages?post=75649"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/coauthors?post=75649"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}