<?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: Harry Kimpel</title>
    <description>The latest articles on Forem by Harry Kimpel (@harrykimpel).</description>
    <link>https://forem.com/harrykimpel</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%2F519398%2F5979e673-0f6e-40ae-8027-d9c7aef6e964.png</url>
      <title>Forem: Harry Kimpel</title>
      <link>https://forem.com/harrykimpel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/harrykimpel"/>
    <language>en</language>
    <item>
      <title>Build AI Agents You Can Actually Trust — Hackathon in Mountain View 🚀</title>
      <dc:creator>Harry Kimpel</dc:creator>
      <pubDate>Wed, 11 Mar 2026 20:12:07 +0000</pubDate>
      <link>https://forem.com/newrelic/build-ai-agents-you-can-actually-trust-hackathon-in-mountain-view-1edf</link>
      <guid>https://forem.com/newrelic/build-ai-agents-you-can-actually-trust-hackathon-in-mountain-view-1edf</guid>
      <description>&lt;h2&gt;
  
  
  You Founded a Startup. Your AI Agents Are Hallucinating. Your Investors Are Watching
&lt;/h2&gt;

&lt;p&gt;Sound fun? It is, actually.&lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;March 27&lt;/strong&gt;, we're hosting a free, in-person hackathon at the &lt;strong&gt;Microsoft Mountain View Campus&lt;/strong&gt; (1045 La Avenida St, Mountain View, CA) where you'll build an AI-powered travel planning assistant from scratch — and then make it production-ready with real observability and security controls.&lt;/p&gt;

&lt;p&gt;This is part of &lt;a href="https://aka.ms/wth" rel="noopener noreferrer"&gt;Microsoft's What The Hack&lt;/a&gt; series: collaborative, challenge-based hackathons where you learn by &lt;em&gt;doing&lt;/em&gt;, not by watching someone else's screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  🌍 The Scenario: Welcome to WanderAI
&lt;/h2&gt;

&lt;p&gt;You've just founded &lt;strong&gt;WanderAI&lt;/strong&gt;, a travel planning startup. Your customers describe their dream trip, and your AI agents craft personalized itineraries.&lt;/p&gt;

&lt;p&gt;But here's the catch — your investors want answers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔍 Are the agents making &lt;em&gt;good&lt;/em&gt; recommendations?&lt;/li&gt;
&lt;li&gt;⚡ How fast are they responding?&lt;/li&gt;
&lt;li&gt;🚨 When something breaks, can we debug it?&lt;/li&gt;
&lt;li&gt;✅ Are the travel plans actually... good?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your mission: go from "cool demo" to "production-ready AI service" in a single day.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ What You'll Build (and Learn)
&lt;/h2&gt;

&lt;p&gt;The hack is structured as 8 progressive challenges:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Challenge&lt;/th&gt;
&lt;th&gt;What You'll Do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;00&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Set up your GitHub Codespace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;01&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Master the Foundations&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Understand agent architecture, tools &amp;amp; orchestration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Build Your MVP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Create a Flask web app + your first AI agent with tool calling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;03&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Add OpenTelemetry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Instrument with built-in telemetry, verify traces in console &amp;amp; New Relic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;04&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;New Relic Integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Custom spans, metrics, and correlated logging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;05&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Monitoring Best Practices&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dashboards, alerting, and production monitoring patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;06&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;LLM Quality Gates&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Build evaluation tests and CI/CD quality gates for AI outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;07&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Platform Security&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Configure Microsoft Foundry Guardrails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;08&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;App-Level Security&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Prompt injection detection and blocking&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;By the end, you'll have a fully instrumented, observable, and secure multi-agent AI system. Not bad for one day.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧰 The Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft Agent Framework&lt;/strong&gt; — for building multi-agent orchestrations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenTelemetry&lt;/strong&gt; — the open standard for traces, metrics, and logs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New Relic&lt;/strong&gt; — for sending, visualizing, and alerting on all that telemetry&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure&lt;/strong&gt; — the cloud backbone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python + Flask&lt;/strong&gt; — the app layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Codespaces&lt;/strong&gt; — zero local setup headaches&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🍕 The Details
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;When:&lt;/strong&gt; March 27, 2026&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Microsoft Mountain View Campus, 1045 La Avenida St, Mountain View, CA&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; Free&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Food:&lt;/strong&gt; Provided&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duration:&lt;/strong&gt; ~3–5 hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What to bring:&lt;/strong&gt; Your laptop and curiosity&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who Is This For?
&lt;/h2&gt;

&lt;p&gt;Anyone who's building (or thinking about building) AI agents and wants to understand what's happening under the hood. Whether you're a backend engineer, an ML practitioner, a platform engineer, or just AI-curious — this hack meets you where you are.&lt;/p&gt;

&lt;p&gt;No prior experience with New Relic or OpenTelemetry is required. Basic Python and web dev knowledge is helpful.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎟️ Register Now
&lt;/h2&gt;

&lt;p&gt;Space is limited — grab your seat before it fills up:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://aka.ms/AzNewRelicWTH" rel="noopener noreferrer"&gt;Register here&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;See you in Mountain View! 🏔️&lt;/p&gt;

</description>
      <category>ai</category>
      <category>observability</category>
      <category>opentelemetry</category>
      <category>newrelic</category>
    </item>
    <item>
      <title>Unleashing the Power of Monitoring: Master Your WordPress with New Relic</title>
      <dc:creator>Harry Kimpel</dc:creator>
      <pubDate>Tue, 25 Nov 2025 19:58:22 +0000</pubDate>
      <link>https://forem.com/newrelic/unleashing-the-power-of-monitoring-master-your-wordpress-with-new-relic-1gjg</link>
      <guid>https://forem.com/newrelic/unleashing-the-power-of-monitoring-master-your-wordpress-with-new-relic-1gjg</guid>
      <description>&lt;p&gt;WordPress powers countless websites across various domains, offering incredible versatility. This Content Management System (CMS) is the undisputed leader in the CMS market, powering an impressive 43.6% of all websites globally, according to &lt;a href="https://growthscribe.com/wordpress-statistics/" rel="noopener noreferrer"&gt;these statistics&lt;/a&gt;. With over 810 million websites built on the platform and hundreds more launching daily (500+), its adoption continues to surge. This widespread use gives WordPress a massive 62% CMS market share, significantly outpacing its rivals.&lt;/p&gt;

&lt;p&gt;However, even the most robust WordPress sites can face performance challenges. Slowdowns are often caused by factors such as slow-loading plugins, database connection issues, infrastructure capacity problems, network trouble, large page assets (like images or fonts), and broken links. This is why robust monitoring is essential for maintaining a fast, reliable, and user-friendly website.&lt;/p&gt;

&lt;p&gt;The question now is: what should be monitored in WordPress?&lt;/p&gt;

&lt;p&gt;To truly master your WordPress environment, monitoring needs to extend across the entire stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WordPress Application Code&lt;/strong&gt;: The core of your site and any custom code or plugins you've added.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Underlying Infrastructure&lt;/strong&gt;: This includes the hosts, servers, or virtual machines (VMs) that power your site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;(Apache) Web Server&lt;/strong&gt;: Monitoring the web server ensures it's handling requests efficiently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;(MySQL) Database&lt;/strong&gt;: Database performance is critical, as slow queries can quickly bottleneck your site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;End-User Experience (Frontend)&lt;/strong&gt;: How your website performs for your actual users, measuring factors like page load times.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business Objectives&lt;/strong&gt;: Tracking metrics like visitor count, device usage, and post performance, which tie directly to your business goals.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Monitor WordPress
&lt;/h2&gt;

&lt;p&gt;You have several powerful options for implementing comprehensive WordPress monitoring:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. New Relic Observability Platform
&lt;/h3&gt;

&lt;p&gt;New Relic offers a powerful &lt;a href="https://docs.newrelic.com/docs/apm/agents/php-agent/frameworks-libraries/wordpress-fullstack-integration/?ref=kimpel.com" rel="noopener noreferrer"&gt;full-stack WordPress integration&lt;/a&gt; that monitors your application's performance. It helps you diagnose issues and optimize your code by leveraging New Relic's existing PHP, Apache, and MySQL integrations. This provides pre-built dashboards with crucial metrics like transactions, visitors, and call duration. The installation process is often surprisingly quick, with agents installed in minutes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fqepp08u3v855x17scd0y.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fqepp08u3v855x17scd0y.gif" alt="New Relic Agent install in less than two minutes" width="525" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. OpenTelemetry (Open-Source Standard)
&lt;/h3&gt;

&lt;p&gt;OpenTelemetry (CNCF project) is a vendor-agnostic set of APIs, libraries, and a collector service designed to standardize how you collect telemetry data (Traces, Metrics, and Logs) from your applications.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Traces&lt;/strong&gt;: Show the path of a request through your application and services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics&lt;/strong&gt;: A measurement captured at runtime, such as Avg. CPU, Throughput, or Max. Memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs&lt;/strong&gt;: A recording of an event.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/opentelemetry-php/contrib-auto-wordpress" rel="noopener noreferrer"&gt;OpenTelemetry WordPress extension&lt;/a&gt; monitors performance and, unlike some WordPress setups, relies on a composer for managing dependencies, including the OpenTelemetry SDK and an exporter to send telemetry data to an observability platform of your choice.What Monitoring Reveals&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of WordPress monitoring
&lt;/h2&gt;

&lt;p&gt;Implementing monitoring gives you deep visibility into your site's performance:&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend Monitoring
&lt;/h3&gt;

&lt;p&gt;This focuses on the user experience, revealing how quickly pages load and identifying performance bottlenecks visible to the end-user.&lt;/p&gt;

&lt;p&gt;New Relic monitoring capabilities automatically include &lt;a href="https://developer.chrome.com/docs/lighthouse/performance/performance-scoring" rel="noopener noreferrer"&gt;Google’s lighthouse metrics&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fi755gequzpzbpyu7jyv3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fi755gequzpzbpyu7jyv3.png" alt="Core Web Vitals dashboard within New Relic UI" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This allows you to either visualize a comparison between frontend and backend duration or dive deeper into the breakdowns of the frontend sections of your page view load time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fe96creghyomccnibkpym.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fe96creghyomccnibkpym.png" alt="Page view load time, Front end vs. Back end response time, and HTTP Error rate dashboards within New Relic UI" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another key aspect of frontend aka real user monitoring is New Relic’s Geography UI page. It provides a world view with color-coded performance information about your frontend experience in cities, regions, and countries anywhere around the world. The map shows your user data by region, so you can visualize your traffic and error hotspots alongside device type information. You can also examine critical Core Web Vitals data so that you can prioritize areas around the globe needing attention.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdquelgn7lbofu7dbni5w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdquelgn7lbofu7dbni5w.png" alt="Average load time by geography within the New Relic UI" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend Analysis
&lt;/h3&gt;

&lt;p&gt;Monitoring provides detailed insight into your server-side performance, allowing you to examine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Golden Signals&lt;/strong&gt;: Web transaction time, throughput, errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ft2kyp4xy4yk8egultx8v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ft2kyp4xy4yk8egultx8v.png" alt="Golden Signals dashboard within the New Relic UI" width="800" height="625"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Especially when seeing spikes, a timeseries view provides visual representations of patterns and anomalies.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdb3lic9zclczkn2xroks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdb3lic9zclczkn2xroks.png" alt="Web transactions time and Throughput dashboards within the New Relic UI" width="800" height="629"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transactions&lt;/strong&gt;: The processing time for various key requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fvjmsro5clk4l4qw7xp67.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fvjmsro5clk4l4qw7xp67.png" alt="Transaction monitoring dashboard within New Relic UI" width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: Slow or inefficient database queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ft43810nrl1lh8y6k4wq6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ft43810nrl1lh8y6k4wq6.png" alt="Database monitoring dashboard within New Relic UI" width="800" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hooks&lt;/strong&gt;: Performance of WordPress action and filter hooks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Frjx2kygm0rfmx2uo7t0e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Frjx2kygm0rfmx2uo7t0e.png" alt="Database monitoring dashboard within New Relic UI" width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Traces&lt;/strong&gt;: Detailed views of a single request's journey through your system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F9hl3vvdqqrhr9y2aeqlr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F9hl3vvdqqrhr9y2aeqlr.png" alt="Trace details dashboard within the New Relic UI" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fq1wxlx9m2pusv98llzet.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fq1wxlx9m2pusv98llzet.png" alt="Query performance analysis within New Relic UI" width="686" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Business Metrics
&lt;/h3&gt;

&lt;p&gt;Beyond technical performance, monitoring allows you to track metrics that directly impact your business:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visitors&lt;/strong&gt;: Analyze user traffic and trends&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fjo3cjm4inzsdzahmgp1q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fjo3cjm4inzsdzahmgp1q.png" alt="Website visitor analytics within the New Relic UI" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Devices&lt;/strong&gt;: See which devices your visitors are using&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fzaa9c468v2lcjw2vho0i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fzaa9c468v2lcjw2vho0i.png" alt="Website visitor device analytics within the New Relic UI" width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Posts&lt;/strong&gt;: Monitor the performance and traffic of specific content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ftozwaivkd6io1dgswhbz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ftozwaivkd6io1dgswhbz.png" alt="Content performance analytics within New Relic UI" width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend Users&lt;/strong&gt;: Track activity and performance related to logged-in users and administrators.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion: Why, What, and How
&lt;/h2&gt;

&lt;p&gt;Effective WordPress monitoring answers three key questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Why Monitor?&lt;/strong&gt; To resolve slowdowns, address infrastructure issues, and ensure a positive end-user experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What to Monitor?&lt;/strong&gt; The technical aspects (code, infrastructure, database), the end-user experience, and business objectives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How to Monitor?&lt;/strong&gt; By utilizing powerful options like the New Relic observability platform or the vendor-agnostic OpenTelemetry standard.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ultimately, monitoring is about understanding where your application is excelling and where it needs attention, allowing you to proactively optimize performance and achieve your business goals.&lt;/p&gt;

&lt;p&gt;Ready to gain deep insights into your WordPress performance?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://newrelic.com/signup?ref=kimpel.com" rel="noopener noreferrer"&gt;Get started with New Relic today&lt;/a&gt;&lt;/strong&gt; and leverage the power of comprehensive observability, whether through our native integrations or the flexibility of OpenTelemetry.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>newrelic</category>
      <category>observability</category>
    </item>
    <item>
      <title>Optimizing Kafka Tracing with OpenTelemetry: Boost Visibility &amp; Performance</title>
      <dc:creator>Harry Kimpel</dc:creator>
      <pubDate>Tue, 25 Nov 2025 19:55:31 +0000</pubDate>
      <link>https://forem.com/newrelic/optimizing-kafka-tracing-with-opentelemetry-boost-visibility-performance-442j</link>
      <guid>https://forem.com/newrelic/optimizing-kafka-tracing-with-opentelemetry-boost-visibility-performance-442j</guid>
      <description>&lt;p&gt;Ideally, you should be using distributed tracing to trace requests through your system, but &lt;a href="https://kafka.apache.org/" rel="noopener noreferrer"&gt;Kafka&lt;/a&gt; decouples producers and consumers, which means there are no direct transactions to trace between them. Kafka also uses asynchronous processes, which have implicit, not explicit, dependencies. That makes it challenging to understand how your microservices are working together.&lt;/p&gt;

&lt;p&gt;However, it is possible to monitor your Kafka clusters with distributed tracing and &lt;a href="https://opentelemetry.io/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt;. You can then analyze and visualize your traces in an open-source distributed tracing tool like Jaeger or a full observability platform like New Relic. In this post, I will leverage a simple application to show how you can achieve this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Considerations &amp;amp; Guidelines
&lt;/h2&gt;

&lt;p&gt;OpenTelemetry typically comes in two flavors:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fn05devmng1isc0qtvxzs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fn05devmng1isc0qtvxzs.png" alt="OTel design considerations and guidelines" width="512" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I talk about these flavors, I typically use the analogy above. You can either buy a ready-made cake and enjoy it or buy all the ingredients and make the cake yourself. With OpenTelemetry, the approach is very similar and the flavors are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero-code instrumentation&lt;/strong&gt;: in this approach, you will use an OpenTelemetry agent and attach it to your application at startup time. This agent will then do its magic and automatically (without any source code changes) provide a lot of telemetry signals (metrics, traces and logs) and insights into your application.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pros&lt;/strong&gt;:
&lt;/li&gt;
&lt;li&gt;Getting started quickly
&lt;/li&gt;
&lt;li&gt;No source code changes
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cons&lt;/strong&gt;:
&lt;/li&gt;
&lt;li&gt;Limited customization
&lt;/li&gt;
&lt;li&gt;Depth of visibility into your application may be limited
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Manual instrumentation&lt;/strong&gt;: this option requires you to add some dependencies and packages to your source code that you need to manage as part of your regular software development lifecycle (SDLC). However, this also allows you to be more specific and custom about your instrumentation. You can easily add custom metrics, traces, attributes to your telemetry.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pros&lt;/strong&gt;:
&lt;/li&gt;
&lt;li&gt;Way more flexible with customizing telemetry
&lt;/li&gt;
&lt;li&gt;Easily able to add, remove and tweak the depth of your instrumentation
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cons&lt;/strong&gt;:
&lt;/li&gt;
&lt;li&gt;Dependencies in your source code
&lt;/li&gt;
&lt;li&gt;More effort to implement&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sample application
&lt;/h2&gt;

&lt;p&gt;The sample application (available in this &lt;a href="https://github.com/harrykimpel/java-kafka-otel-producer-consumer" rel="noopener noreferrer"&gt;public GitHub repository&lt;/a&gt;) that I am using in this blog is based on this high-level architecture:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fh2ldd9krkdki54pe0c8q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fh2ldd9krkdki54pe0c8q.png" alt="APM entity map" width="512" height="155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It contains these components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;kafka-java-producer&lt;/strong&gt;: a Java Spring Boot application that produces messages into a Kafka topic
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kafka broker&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;kafka-java-consumer&lt;/strong&gt;: a Java Spring Boot application that subscribes to a Kafka topic and reads messages from it. This component also makes calls to an external REST API service (that is not in our control)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;kafka-java-service&lt;/strong&gt;: a downstream Java Spring Boot application that is being called from the consumer service&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Zero-code instrumentation
&lt;/h2&gt;

&lt;p&gt;Let’s start with zero-code instrumentation, aka automatic instrumentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;Each of the different services contain a &lt;code&gt;run.sh&lt;/code&gt; script to get the service up and running. The script looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fqccp4bxre8lsehdb8f92.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fqccp4bxre8lsehdb8f92.png" alt="run script Java tool options" width="512" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key line in this is the first one. Here we are defining the &lt;code&gt;JAVA_TOOL_OPTIONS&lt;/code&gt; and configuring the &lt;code&gt;-javaagent&lt;/code&gt; to point to the location of the &lt;a href="https://github.com/open-telemetry/opentelemetry-java" rel="noopener noreferrer"&gt;OpenTelemetry Java agent&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The next three lines configure how we want to deal with the different telemetry signals. In our case, I define the traces, metrics and logs to be exported via OpenTelemetry Line Protocol (OTLP).&lt;/p&gt;

&lt;p&gt;There are three additional environment variables that are quite important to configure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/strong&gt;: the target system where we want to export the data to, i.e. our telemetry backend. In my case, this is for sure New Relic and so I configure New Relic’s native OTLP endpoint
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OTEL_EXPORTER_OTLP_HEADERS&lt;/strong&gt;: the above exporter endpoint is an open API, so we need to configure an API key. In the case of New Relic, this is a New Relic license key.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OTEL_SERVICE_NAME&lt;/strong&gt;: ideally, we want to give the service a meaningful name, so that New Relic can create an appropriate entity from it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is basically all we need to configure. Everything else is dealt by the OpenTelemetry Java agent. No need to change anything in our source code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability
&lt;/h3&gt;

&lt;p&gt;Let’s see what level of visibility into the services we can achieve from zero-code instrumentation.&lt;/p&gt;

&lt;p&gt;When navigating to my New Relic account, I can see all services reporting into separate entities.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fb4unlp4fo63yl9gpybq5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fb4unlp4fo63yl9gpybq5.png" alt="New Relic all entities" width="512" height="295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s start by exploring the &lt;strong&gt;kafka-java-producer&lt;/strong&gt; service.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Summary&lt;/strong&gt; view offers a great overview of all the most important telemetry and metrics I should be focusing on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fnjipojvwykrwyc8350tu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fnjipojvwykrwyc8350tu.png" alt="New Relic APM summary UI" width="512" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As part of this blog, I am mostly interested in the &lt;strong&gt;Distributed Tracing&lt;/strong&gt; section, so let’s dive deeper into this area.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F1zt2yqi4rk74kom3y4bg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F1zt2yqi4rk74kom3y4bg.png" alt="New Relic APM distributed tracing UI" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By looking at a single trace, this allows me to view the detailed information on how long this specific trace took to execute and where the time was spent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fnc77i70lb464lslqy5yj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fnc77i70lb464lslqy5yj.png" alt="New Relic APM distributed tracing trace details UI" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also automatically draw an &lt;strong&gt;Entity map&lt;/strong&gt; of all the different services involved in a given trace.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F1xqxeod65yiodgav44r3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F1xqxeod65yiodgav44r3.png" alt="New Relic APM distributed tracing trace details entity map" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The interesting area that I want to draw your attention to lies in the &lt;a href="https://opentelemetry.io/docs/specs/otel/overview/#traces" rel="noopener noreferrer"&gt;trace&lt;/a&gt; and &lt;a href="https://opentelemetry.io/docs/specs/otel/overview/#spans" rel="noopener noreferrer"&gt;span&lt;/a&gt; breakdown. You can see how the trace gets initiated on the producer, the consumer then picks up the message and how the consumer then also makes two separate calls to the downstream service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdm57g9uhlfjfc2dnqvp7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdm57g9uhlfjfc2dnqvp7.png" alt="New Relic APM distributed tracing trace details spans zero-code" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What is interesting here is the span that says “Uninstrumented time”. This is code in the consumer where the agent was not able to capture some more detailed information about what is going on in its internal methods.&lt;/p&gt;

&lt;p&gt;This already shows the limits of zero-code instrumentation. The agent by default will not instrument all the various methods and source code, but rather stops - by design - at some level to get deeper visibility into your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manual instrumentation
&lt;/h2&gt;

&lt;p&gt;In the previous section, you saw how zero-code instrumentation has some limits when it comes to visibility into your application. This is exactly where manual instrumentation comes into play.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;I have configured the same application, but this time, no agent at all is configured when starting the application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fhi54flnmwfqwoepqgor0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fhi54flnmwfqwoepqgor0.png" alt="run script Maven wrapper" width="800" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I simply use the Maven wrapper to run the application.&lt;/p&gt;

&lt;p&gt;The other configuration details are then part of my application.properties:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F9fw5s7bbjmxzccdaktfx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F9fw5s7bbjmxzccdaktfx.png" alt="Java application.properties" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These properties are then used in my Spring Boot application code to define the configuration for OpenTelemetry for traces, metrics and logs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fel2yxupwhksv6por0p6m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fel2yxupwhksv6por0p6m.png" alt="Java OpenTelemetry SDK configuration" width="800" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability
&lt;/h3&gt;

&lt;p&gt;Before I jump into the details of how I implemented some manual instrumentation, let’s have a look at the result first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F85i6px8wbw63a2inp7z7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F85i6px8wbw63a2inp7z7.png" alt="New Relic APM distributed tracing trace details spans manual" width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do you notice how the span, which previously was called out with “Uninstrumented time”, now shows much more detailed information? I now can see these additional spans:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ExecuteLongRunningTask
&lt;/li&gt;
&lt;li&gt;WhyTheHeckDoWeSleepHere
&lt;/li&gt;
&lt;li&gt;SomeTinyTask
&lt;/li&gt;
&lt;li&gt;AnotherShortRunningTask&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The one that says “WhyTheHeckDoWeSleepHere” seems to be taking the most time. No wonder, as the name suggests 😉.&lt;/p&gt;

&lt;p&gt;Let’s have a look at the source code to reveal the manual instrumentation I put in place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F5z2y7g71fdr64g34tzii.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F5z2y7g71fdr64g34tzii.png" alt="Java source code ewith custom OTel spans" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the method named &lt;strong&gt;ExecuteLongRunningTask&lt;/strong&gt; I have created a new span on the current tracer by using the &lt;strong&gt;spanBuilder()&lt;/strong&gt; Method.&lt;/p&gt;

&lt;p&gt;In addition to that, you may also notice that - just for the fun of it - I created another span called “WhyTheHeckDoWeSleepHere” that contains an artificial unit of work or rather a sleep instruction on the current thread.&lt;/p&gt;

&lt;p&gt;These concepts to leverage the OpenTelemetry SDK allow me to be much more specific in getting insights and details into my application and source code. But, as you can imagine, also have the caveat that I need to have some dependencies and custom code available in my source code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope I was able to show you how easy it can be to leverage OpenTelemetry in order to get insights into your application and services. We looked into zero-code instrumentation to get started without any code changes, but the level of details may be limited. We then also looked into manual instrumentation. This allowed us to be more specific and customize the instrumentation, but the effort to get started is a little higher.&lt;/p&gt;

&lt;p&gt;I encourage you to have a look into OpenTelemetry and its fascinating capabilities. Let me know your thoughts and please get in touch if you have any questions or need further information.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>kafka</category>
      <category>opentelemetry</category>
      <category>newrelic</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Why I'm Excited for .NET Conf 2024</title>
      <dc:creator>Harry Kimpel</dc:creator>
      <pubDate>Tue, 12 Nov 2024 11:45:46 +0000</pubDate>
      <link>https://forem.com/harrykimpel/why-im-excited-for-net-conf-2024-56mo</link>
      <guid>https://forem.com/harrykimpel/why-im-excited-for-net-conf-2024-56mo</guid>
      <description>&lt;p&gt;Greetings fellow .NET enthusiasts and tech aficionados! With the highly-anticipated .NET Conf 2024 just around the corner, I find myself reflecting on how far we've come in the world of .NET development and what thrilling innovations are yet to come.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Journey Through .NET
&lt;/h2&gt;

&lt;p&gt;Let's take a quick stroll down memory lane. My experience with .NET technologies dates back to 2002, when I first dipped my toes into the vast sea of possibilities offered by .NET. Over the years, I've seen it transform from a promising framework into a powerhouse that underpins countless applications worldwide. Every upgrade has brought something new and exciting, fueling my passion for staying on the cutting edge of technology.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spotlight on .NET 9
&lt;/h2&gt;

&lt;p&gt;Fast forward to today, one of the highlights I'm most eager about at this year's conference is the release of .NET 9. The advancements and features in .NET 9 promise to revolutionize how we build, deploy, and manage applications. From performance improvements to enhanced security features, .NET 9 is set to redefine efficiency, enabling developers to create more robust and scalable solutions.&lt;/p&gt;

&lt;p&gt;But what truly excites me is the introduction of and this year's updates on .NET Aspire, a suite of tools designed to streamline the development process, especially for large-scale applications. This groundbreaking addition not only underscores Microsoft's commitment to innovation but also provides developers with the power to push boundaries and elevate their craft.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exploring Blazor and .NET MAUI
&lt;/h2&gt;

&lt;p&gt;Another reason I'm counting the days to .NET Conf 2024 is to learn more about the latest advancements in Blazor and .NET MAUI. Both technologies have been game-changers in their own right, and I'm particularly interested in seeing how new features will enhance our ability to create cross-platform applications with seamless user experiences.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Role of AI and Emerging Technologies
&lt;/h2&gt;

&lt;p&gt;This year, there's a dedicated spotlight on AI and its integration into the .NET ecosystem. Artificial Intelligence is a frontier that holds immense potential, and exploring its convergence with .NET will undoubtedly open doors to innovative applications and smarter solutions. The prospect of using .NET to enhance AI capabilities and vice versa is an exhilarating thought, aligning perfectly with the forward-thinking spirit that drives our community.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting With the .NET Community
&lt;/h2&gt;

&lt;p&gt;Beyond the technical sessions and keynotes, .NET Conf offers an unparalleled opportunity to connect with fellow developers and technology leaders. Engaging with this vibrant community is always a highlight of the conference for me—it's where shared knowledge, collaborations, and lifelong friendships are born.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;.NET Conf 2024 promises to be a landmark event, brimming with insights, revelations, and opportunities to explore the future of .NET development. I hope you're as excited as I am to discover what's in store and to continue pushing the boundaries of what's possible with .NET technologies.&lt;/p&gt;

&lt;p&gt;If you’re attending the conference, I’d love to hear what sessions you’re looking forward to and how .NET continues to inspire your work. Let's connect and share our experiences as we chart our course through the evolving landscape of .NET development. Here's to exploring new horizons with .NET!&lt;/p&gt;

&lt;p&gt;Feel free to reach out or drop your thoughts in the comments below. Until then, happy coding!&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>aspire</category>
      <category>blazor</category>
    </item>
    <item>
      <title>Observability as code for AI apps with New Relic and Pulumi</title>
      <dc:creator>Harry Kimpel</dc:creator>
      <pubDate>Mon, 14 Oct 2024 18:30:45 +0000</pubDate>
      <link>https://forem.com/newrelic/observability-as-code-for-ai-apps-with-new-relic-and-pulumi-5fgo</link>
      <guid>https://forem.com/newrelic/observability-as-code-for-ai-apps-with-new-relic-and-pulumi-5fgo</guid>
      <description>&lt;p&gt;To read this full article, &lt;a href="https://newrelic.com/blog/how-to-relic/observability-as-code-ai-apps-new-relic-pulumi?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q3-devtoupdates" rel="noopener noreferrer"&gt;click here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;AI applications are complex and distributed, making effective monitoring challenging. Combining the New Relic intelligent observability platform with Pulumi's infrastructure-as-code and secret management solutions allows for an end-to-end "observability as code" approach. This method enables teams to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define artificial intelligence (AI) and large language model (LLM) monitoring instrumentation along with cloud resources programmatically.&lt;/li&gt;
&lt;li&gt;Securely manage API keys and cloud account credentials.&lt;/li&gt;
&lt;li&gt;Automatically deploy New Relic instrumentation alongside AI applications and infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consistent monitoring across environments&lt;/li&gt;
&lt;li&gt;Version-controlled observability configuration&lt;/li&gt;
&lt;li&gt;Easier detection of performance issues&lt;/li&gt;
&lt;li&gt;Deeper insights into AI model behavior and resource usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The observability-as-code approach helps developers maintain visibility into their AI applications as they scale and evolve.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Pulumi?
&lt;/h2&gt;

&lt;p&gt;Pulumi provides a range of products and services for platform engineers and developers, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/product/" rel="noopener noreferrer"&gt;&lt;strong&gt;Pulumi infrastructure as code (IaC)&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;:&lt;/strong&gt; An open-source tool for defining cloud infrastructure. It supports multiple programming languages. For example, you can use Python to declare AWS Fargate services, Pinecone indexes, and custom New Relic dashboards.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/docs/pulumi-cloud/" rel="noopener noreferrer"&gt;&lt;strong&gt;Pulumi Cloud&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;:&lt;/strong&gt; A hosted service that provides additional features on top of the open-source tool, such as state and secrets management, team collaboration, policy enforcement, and an AI-powered chat assistant, &lt;a href="https://www.pulumi.com/product/copilot/" rel="noopener noreferrer"&gt;&lt;strong&gt;Pulumi Copilot&lt;/strong&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/docs/esc/" rel="noopener noreferrer"&gt;&lt;strong&gt;Pulumi Environments, Secrets, and Configuration (ESC)&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;:&lt;/strong&gt; This ensures the secure management of sensitive information necessary for observability. For example, you can manage New Relic, OpenAI, and Pinecone API keys and configure OpenID Connect (OIDC) in AWS. This service is also part of Pulumi Cloud.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pulumi allows teams to version control their observability configurations and infrastructure definitions. This ensures consistency across environments and simplifies the correlation of application changes with monitoring updates and underlying infrastructure modifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to achieve observability as code with New Relic and Pulumi
&lt;/h2&gt;

&lt;p&gt;In this guide, you’ll add &lt;a href="https://newrelic.com/platform/ai-monitoring?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q3-devtoupdates" rel="noopener noreferrer"&gt;AI and LLM monitoring&lt;/a&gt; capabilities to an existing chat application by configuring the New Relic application performance monitoring (APM) agent, and by defining New Relic dashboards in Python using Pulumi.&lt;/p&gt;

&lt;p&gt;The final version of the application and infrastructure referenced throughout the guide resides in the &lt;a href="https://github.com/desteves/ai-chat-app" rel="noopener noreferrer"&gt;AI chat app public GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before you start
&lt;/h3&gt;

&lt;p&gt;Ensure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://newrelic.com/signup?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q3-devtoupdates" rel="noopener noreferrer"&gt;New Relic account&lt;/a&gt; and a valid &lt;a href="https://docs.newrelic.com/docs/apis/intro-apis/new-relic-api-keys/#overview-keys?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q3-devtoupdates" rel="noopener noreferrer"&gt;New Relic license key&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://app.pulumi.com/signup" rel="noopener noreferrer"&gt;Pulumi Cloud account&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://www.pulumi.com/docs/install/" rel="noopener noreferrer"&gt;Pulumi CLI&lt;/a&gt; installed locally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;All the services used throughout this guide qualify under the respective free tiers.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Explore the OpenAI demo application
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/desteves/ai-chat-app" rel="noopener noreferrer"&gt;OpenAI demo application&lt;/a&gt; used throughout this guide is written in Node.js for the backend and Python for the frontend. It interacts with OpenAI to generate various gameplays through generative conversational AI interactions. It uses the public OpenAI platform to call its API to access different LLMs like GPT-3.5 Turbo, GPT-4 Turbo, and GPT-4o.&lt;/p&gt;

&lt;p&gt;Below is a screenshot of a "higher or lower" gameplay:&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%2Fk1h8gbai4wa2bxgdrp6l.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%2Fk1h8gbai4wa2bxgdrp6l.png" alt="Image description" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The demo simulates a retrieval-augmented generation (RAG) flow, which commonly looks up information that the AI either doesn't know or might hallucinate. The app stores a handful of common game names and instructions in a Pinecone vector database and then uses it as an embedding when calling OpenAI.&lt;/p&gt;

&lt;p&gt;This application is configured to observe its performance, such as traces, metrics, and logs. It leverages &lt;a href="https://newrelic.com/blog/nerdlog/ai-monitoring-ga?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q3-devtoupdates" rel="noopener noreferrer"&gt;New Relic's latest innovation in monitoring AI interactions&lt;/a&gt;, such as requests and responses. This capability ensures compliance, promotes quality, and observes your AI costs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Run the app locally with Docker Compose
&lt;/h4&gt;

&lt;p&gt;The easiest way to run the chat on your local machine is via Docker Compose. Inspect the &lt;code&gt;docker-compose.yml&lt;/code&gt; file in the &lt;code&gt;./app&lt;/code&gt; folder and create the required &lt;code&gt;.env&lt;/code&gt; file as shown. Then, in your terminal, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;app 
docker compose up &lt;span class="se"&gt;\-&lt;/span&gt;d –build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, the web service will run on &lt;a href="http://localhost:8888/" rel="noopener noreferrer"&gt;http://localhost:8888/&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Try out the API endpoints
&lt;/h4&gt;

&lt;p&gt;The API backend provides various endpoints that expose all of its AI functionality. The frontend leverages these endpoints to simulate a flow of activity for end users to select a game and initiate a game interaction, that is, play the game, with OpenAI.&lt;/p&gt;

&lt;p&gt;Follow these steps to simulate a higher or lower gameplay:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the web service &lt;a href="http://localhost:8888/" rel="noopener noreferrer"&gt;http://localhost:8888/&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Get Games&lt;/strong&gt; button. A list of games is displayed.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Input&lt;/strong&gt; field, copy &lt;strong&gt;higher or lower&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;To retrieve a game prompt for the next step, click &lt;strong&gt;Get Game Prompt&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;To send the OpenAI request to interact with the game, click &lt;strong&gt;Submit Prompt&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Enter your first guess into the &lt;strong&gt;Insert Your Game Interaction&lt;/strong&gt; textbox. You’ll get a message about whether your guess was correct when compared to the number the AI picked. Repeat this step until you have guessed the correct number.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Configure New Relic agents with AI
&lt;/h3&gt;

&lt;p&gt;The chat application is configured to capture telemetry using the New Relic APM agent for API and web services. This agent also leverages New Relic's latest innovation in monitoring AI interactions, such as requests and responses. This capability ensures compliance, promotes quality, and observes AI costs.&lt;/p&gt;

&lt;p&gt;Enable the &lt;a href="https://docs.newrelic.com/docs/ai-monitoring/customize-agent-ai-monitoring/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q3-devtoupdates" rel="noopener noreferrer"&gt;New Relic AI monitoring&lt;/a&gt; capabilities through the &lt;a href="https://docs.newrelic.com/docs/ai-monitoring/customize-agent-ai-monitoring/#nodejs-config?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q3-devtoupdates" rel="noopener noreferrer"&gt;New Relic APM agent configuration&lt;/a&gt; or via environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;NEW_RELIC_AI_MONITORING_ENABLED &lt;span class="o"&gt;=&lt;/span&gt; TRUE
NEW_RELIC_SPAN_EVENTS_MAX_SAMPLES_STORED &lt;span class="o"&gt;=&lt;/span&gt; 10000
NEW_RELIC_CUSTOM_INSIGHTS_EVENTS_MAX_SAMPLES_STORED &lt;span class="o"&gt;=&lt;/span&gt; 100000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before exploring the data streamed by the New Relic agents, you’ll use Pulumi to deploy the application to AWS and create custom dashboards. This will enable you to simulate global traffic interacting with the application, thus generating representative AI metrics to forecast performance and cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Manage your secrets with Pulumi ESC
&lt;/h3&gt;

&lt;p&gt;While running everything locally is always a good first step in the software development lifecycle, moving to a cloud test environment and beyond involves ensuring .env files are securely available and all the application infrastructure dependencies are deployed and configured. The "Ops" side of DevOps comes into the picture, but it need not be a daunting task. You’ll leverage Pulumi ESC to manage the chat application's secrets.&lt;/p&gt;

&lt;h4&gt;
  
  
  Create a Pulumi ESC Environment
&lt;/h4&gt;

&lt;p&gt;You'll create a Pulumi ESC environment to store all your .env secrets in Pulumi Cloud. This enables teams to share sensitive information with authorized accounts. Ensure that your New Relic license key, OpenAI token, and Pinecone API keys are handy.&lt;/p&gt;

&lt;p&gt;In your terminal, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi login
&lt;span class="nv"&gt;E&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-cool-chat-app-env
pulumi &lt;span class="nb"&gt;env &lt;/span&gt;init &lt;span class="nv"&gt;$E&lt;/span&gt; &lt;span class="nt"&gt;--non-interactive&lt;/span&gt;
pulumi &lt;span class="nb"&gt;env set&lt;/span&gt;  &lt;span class="nv"&gt;$E&lt;/span&gt; environmentVariables.NEW_RELIC_LICENSE_KEY 123ABC &lt;span class="nt"&gt;--secret&lt;/span&gt;
pulumi &lt;span class="nb"&gt;env set&lt;/span&gt;  &lt;span class="nv"&gt;$E&lt;/span&gt; environmentVariables.OPENAI_API_KEY 123ABC &lt;span class="nt"&gt;--secret&lt;/span&gt;
pulumi &lt;span class="nb"&gt;env set&lt;/span&gt;  &lt;span class="nv"&gt;$E&lt;/span&gt; environmentVariables.PINECONE_API_KEY 123ABC &lt;span class="nt"&gt;--secret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Load your .env file with the Pulumi CLI
&lt;/h4&gt;

&lt;p&gt;Now that you’ve defined your ESC environment, you can consume it in several ways. For instance, you can populate our .env files by opening the environment using dotenv formatting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ./app
pulumi &lt;span class="nb"&gt;env &lt;/span&gt;open &lt;span class="nv"&gt;$E&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; dotenv &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./.env
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; –build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Pulumi commands may be scripted to run inside an Amazon EC2 instance or AWS Fargate. Next, you’ll define AWS resources using Pulumi IaC and Python so that you can run the application in AWS. To learn more, visit the &lt;a href="https://www.pulumi.com/docs/esc/" rel="noopener noreferrer"&gt;Pulumi ESC documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate infrastructure code with Pulumi Copilot
&lt;/h3&gt;

&lt;p&gt;Nowadays, you don’t need to start from scratch when using infrastructure as code. Instead, you’ll use the power of generative AI to write Python code to declare all the cloud resources needed for our chat application. This entails declaring New Relic, AWS, and Pinecone resources.&lt;/p&gt;

&lt;p&gt;Pulumi Copilot is a conversational chat interface integrated into Pulumi Cloud. It can assist with Pulumi IaC authoring and deployment. Let's have an intelligent conversation with Pulumi Copilot to help us start writing a Python-based Pulumi program that will also have access to the previously created ESC environment.&lt;/p&gt;

&lt;p&gt;Prompt Pulumi Copilot with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can you help me create a new and empty Python Pulumi project called "my-cool-chat-app" with a new stack called dev? Add "my-cool-chat-app-provider-creds" to the imports in the dev stack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Declare New Relic AI LLM monitoring dashboards
&lt;/h4&gt;

&lt;p&gt;A standard method for sharing dashboards is through the &lt;a href="https://docs.newrelic.com/docs/query-your-data/explore-query-data/dashboards/dashboards-charts-import-export-data/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q3-devtoupdates" rel="noopener noreferrer"&gt;JSON copy and import feature&lt;/a&gt;. However, importing these through the console can result in reproducibility issues over time. For example, what happens when the dashboard JSON definition changes in an undesirable way? It becomes a challenge because there are no versioning details readily available.&lt;/p&gt;

&lt;p&gt;Instead, using a declarative approach to "import" the JSON file unlocks several benefits. It allows for managing the dashboard's lifecycle (creation, deletion, updates) through code, thereby tracking changes over time. Also, it makes it easy to incorporate into deployment pipelines and share these across teams.&lt;/p&gt;

&lt;p&gt;You have an AI/LLM monitoring dashboard JSON file and want to use it to create a new dashboard under our New Relic account. Let's continue our chat with Pulumi Copilot and prompt it to update the current solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Okay great! Can you update the Python code to deploy a New Relic dashboard based on an existing JSON file I will provide as input?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Declare a Pinecone index
&lt;/h4&gt;

&lt;p&gt;A Pinecone index was needed beforehand to test the chat application locally. Let's ensure its existence before deploying the chat application to the cloud.&lt;/p&gt;

&lt;p&gt;Let's ask Pulumi Copilot to define this resource on our behalf:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Thank you! I also need a serverless Pinecone index named "games" in the "default" namespace on AWS us-east-1 with 1536 dimensions. Can you generate the Python code to define this resource?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Declare an Amazon EC2 instance
&lt;/h4&gt;

&lt;p&gt;To test the chat application in a cloud environment, you'll use an EC2 instance. Let's ask Pulumi Copilot to define all the AWS resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Perfect! I also want to deploy my chat application to an Amazon EC2 Linux instance. 
Create a VPC, security group, public subnet, and route table for the EC2 instance.
Ensure the security group allows inbound SSH traffic on port 22 from anywhere.
Ensure the EC2 instance is publicly accessible and runs a Docker Compose command to start the application.
Associate the EC2 instance with a public IP
Update the EC2 instance with a depends_on resource option for the route table association.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Deploy the application with Pulumi
&lt;/h4&gt;

&lt;p&gt;You’ve asked Copilot to help with the Python infrastructure code, so it's time to deploy our application. In the chat window, expand the &lt;strong&gt;Pulumi Code&lt;/strong&gt; drop-down. Click the &lt;strong&gt;Deploy with Pulumi&lt;/strong&gt; button to create a new project.&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%2Fky1lr1diwfhtwn8dnj04.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%2Fky1lr1diwfhtwn8dnj04.png" alt="Image description" width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you download the project, you'll add credentials, modify the code slightly, and deploy our application on AWS.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the &lt;strong&gt;Deploy with Pulumi&lt;/strong&gt; button to create a new project. Then,&lt;/li&gt;
&lt;li&gt;Choose the &lt;strong&gt;CLI Deployment&lt;/strong&gt; deployment method and click &lt;strong&gt;Create Project&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Follow step 3 from the "Get started" steps in the next window to download the project onto your development environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given that you already have a relatively small application in a GitHub repository, you’ll add a new empty folder at the root level named &lt;strong&gt;infra&lt;/strong&gt;, where you'll unzip the contents of the Pulumi project. Compare your solution with the &lt;a href="https://github.com/desteves/ai-chat-app/tree/main/infra" rel="noopener noreferrer"&gt;final version hosted on GitHub&lt;/a&gt; and fix any minor details Copilot may have overlooked.&lt;/p&gt;

&lt;p&gt;Also, include the following changes from the final version shared:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update the custom &lt;a href="https://github.com/desteves/ai-chat-app/blob/main/infra/aws_module.py#L97" rel="noopener noreferrer"&gt;user_data&lt;/a&gt; script.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/desteves/ai-chat-app/blob/main/infra/newrelic_module.py#L12" rel="noopener noreferrer"&gt;Update the dashboard.json to include your New Relic account id&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Optionally, include the &lt;a href="https://github.com/desteves/ai-chat-app/blob/main/infra/docker_build_module.py" rel="noopener noreferrer"&gt;Docker build code&lt;/a&gt; to build and push the images to Dockerhub.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To learn more, visit the &lt;a href="https://www.pulumi.com/docs/pulumi-cloud/copilot/" rel="noopener noreferrer"&gt;Pulumi Copilot documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In order for Pulumi to deploy all the declared resources, it needs access to your cloud accounts. These credentials will reside in a Pulumi ESC Environment named "my-cool-chat-app-provider-creds". Refer to the &lt;a href="https://github.com/desteves/ai-chat-app/tree/main?tab=readme-ov-file#2-store-infra-secrets-in-pulumi-esc" rel="noopener noreferrer"&gt;README&lt;/a&gt; to configure the Environment and set up the Python virtual environment before deploying everything via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi up &lt;span class="nt"&gt;--stack&lt;/span&gt; dev  &lt;span class="nt"&gt;--yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It takes about a minute for all resources to be created. Once created, access the public URL displayed and run load tests.&lt;/p&gt;

&lt;p&gt;Example partial output from the above command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;     Type                                Name                        Status              
 +   pulumi:pulumi:Stack                 my-cool-chat-app-dev-dev    created &lt;span class="o"&gt;(&lt;/span&gt;57s&lt;span class="o"&gt;)&lt;/span&gt;       
 +   ├─ newrelic:index:OneDashboardJson  my_cool_dashboard           created &lt;span class="o"&gt;(&lt;/span&gt;2s&lt;span class="o"&gt;)&lt;/span&gt;        
 +   ├─ pinecone:index:PineconeIndex     my_cool_index               created &lt;span class="o"&gt;(&lt;/span&gt;7s&lt;span class="o"&gt;)&lt;/span&gt;        
 +   ├─ docker-build:index:Image         ai-chat-demo-api            created &lt;span class="o"&gt;(&lt;/span&gt;2s&lt;span class="o"&gt;)&lt;/span&gt;        
 +   ├─ docker-build:index:Image         ai-chat-demo-web            created &lt;span class="o"&gt;(&lt;/span&gt;3s&lt;span class="o"&gt;)&lt;/span&gt;        
 +   ├─ aws:ec2:Vpc                      my_cool_vpc                 created &lt;span class="o"&gt;(&lt;/span&gt;13s&lt;span class="o"&gt;)&lt;/span&gt;       
 +   ├─ aws:ec2:Subnet                   my_cool_subnet              created &lt;span class="o"&gt;(&lt;/span&gt;11s&lt;span class="o"&gt;)&lt;/span&gt;       
 +   ├─ aws:ec2:SecurityGroup            my_cool_security_group      created &lt;span class="o"&gt;(&lt;/span&gt;4s&lt;span class="o"&gt;)&lt;/span&gt;        
 +   ├─ aws:ec2:InternetGateway          my_cool_igw                 created &lt;span class="o"&gt;(&lt;/span&gt;1s&lt;span class="o"&gt;)&lt;/span&gt;        
 +   ├─ aws:ec2:RouteTable               my_cool_route_table         created &lt;span class="o"&gt;(&lt;/span&gt;2s&lt;span class="o"&gt;)&lt;/span&gt;        
 +   ├─ aws:ec2:RouteTableAssociation    my-route-table-association  created &lt;span class="o"&gt;(&lt;/span&gt;0.89s&lt;span class="o"&gt;)&lt;/span&gt;     
 +   └─ aws:ec2:Instance                 my_cool_instance            created &lt;span class="o"&gt;(&lt;/span&gt;24s&lt;span class="o"&gt;)&lt;/span&gt;       

Outputs:
    url: &lt;span class="s2"&gt;"52.41.60.240"&lt;/span&gt;

Resources:
    + 12 created

Duration: 1m0s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explore your New Relic AI LLM dashboards
&lt;/h3&gt;

&lt;p&gt;To recap, the chat application is configured to observe its performance, such as traces, metrics, and logs, using the New Relic APM agent and the infrastructure agent. Now that we’ve simulated traffic, let's review the collected telemetry data in New Relic.&lt;/p&gt;

&lt;h4&gt;
  
  
  AI Response metrics
&lt;/h4&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%2F0araqy5b1kgj9gtejc8p.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%2F0araqy5b1kgj9gtejc8p.png" alt="Image description" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;AI Responses&lt;/strong&gt; section will highlight key metrics when observing your AI/LLM applications. It includes data such as &lt;strong&gt;Total responses&lt;/strong&gt;, &lt;strong&gt;Response time&lt;/strong&gt;, &lt;strong&gt;Token usage per response,&lt;/strong&gt; and &lt;strong&gt;Errors&lt;/strong&gt; within your AI interactions. Time series graphs show you the same information with some more historical context.&lt;/p&gt;

&lt;p&gt;The bottom part of that screen provides more insights into the requests and responses your end customers used to interact with the chat application.&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%2F2mcd3vno5coxlzuqib2c.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%2F2mcd3vno5coxlzuqib2c.png" alt="Image description" width="800" height="223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  AI Model comparison
&lt;/h4&gt;

&lt;p&gt;Another very important aspect of New Relic AI monitoring is the &lt;strong&gt;Model Inventory&lt;/strong&gt; section.&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%2Fw7rf8xbbsbo6v7pk0qpi.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%2Fw7rf8xbbsbo6v7pk0qpi.png" alt="Image description" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This view provides an intuitive overview of all your AI/LLM models being leveraged in our chat application. As you can see, we ran the same application with OpenAI models &lt;strong&gt;GPT-3.5 Turbo&lt;/strong&gt;, &lt;strong&gt;GPT-4 Turbo,&lt;/strong&gt; and &lt;strong&gt;GPT-4o&lt;/strong&gt;. The AI model used is reflected in the &lt;a href="https://github.com/desteves/ai-chat-app/blob/ae7090cbc6d0b6d2adcdc72d6f4d50aa22121ff9/app/api/src/ai.ts#L13" rel="noopener noreferrer"&gt;chatModel variable for the API AI backend.&lt;/a&gt; This view shows you all the critical aspects of the performance, quality of responses, errors, and cost at a glance.&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%2F3une1ic5pqjyc2dqqiuc.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%2F3une1ic5pqjyc2dqqiuc.png" alt="Image description" width="800" height="998"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When combining the raw data from AI monitoring and custom &lt;a href="https://docs.newrelic.com/docs/logs/ui-data/lookup-tables-ui/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q3-devtoupdates" rel="noopener noreferrer"&gt;lookup tables&lt;/a&gt;, New Relic also allows you to build custom dashboards (see Deploy the application with Pulumi for a reference to import custom dashboards). These custom dashboards can bring additional data, such as the actual &lt;a href="https://openai.com/api/pricing/" rel="noopener noreferrer"&gt;cost (in $) for my OpenAI platform&lt;/a&gt;, and leverage the input and output tokens from our monitoring to calculate the total AI cost for running the chat application.&lt;/p&gt;

&lt;h4&gt;
  
  
  OpenAI custom dashboards
&lt;/h4&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%2Fyipsmcu56vpjojdegqxb.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%2Fyipsmcu56vpjojdegqxb.png" alt="Image description" width="800" height="971"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;In this guide, you explored an AI demo application powered by OpenAI and Pinecone. The application uses New Relic AI dashboards to monitor costs and other performance metrics. You used Pulimi Copilot to generate all the cloud components needed to run the chat application in AWS successfully while storing all sensitive information in Pulumi ESC.&lt;/p&gt;

&lt;p&gt;Join us in the &lt;a href="https://www.pulumi.com/resources/observability-as-code-for-ai-apps-new-relic/" rel="noopener noreferrer"&gt;Observability as Code for AI Apps with New Relic and Pulumi&lt;/a&gt; virtual workshop, where you'll see the chat application, Pulumi, and New Relic in action.&lt;/p&gt;

</description>
      <category>observability</category>
      <category>ai</category>
      <category>infrastructureascode</category>
    </item>
    <item>
      <title>Using .NET Aspire eShop application to collect all the telemetry</title>
      <dc:creator>Harry Kimpel</dc:creator>
      <pubDate>Mon, 10 Jun 2024 08:15:37 +0000</pubDate>
      <link>https://forem.com/newrelic/using-net-aspire-eshop-application-to-collect-all-the-telemetry-1olp</link>
      <guid>https://forem.com/newrelic/using-net-aspire-eshop-application-to-collect-all-the-telemetry-1olp</guid>
      <description>&lt;p&gt;To read this full article, &lt;a href="https://newrelic.com/blog/how-to-relic/using-net-aspire-eshop-application-to-collect-all-the-telemetry?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q1-devtoupdates"&gt;click here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Learn how to collect all the telemetry from the .NET Aspire eShop application and send it to an OpenTelemetry backend such as New Relic&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview"&gt;.NET Aspire&lt;/a&gt; is the new kid on the block when it comes to an opinionated, cloud-ready stack for building observable, production-ready, distributed applications. Having a built-in dashboard for the monitoring data is nice during development. But how do you configure OpenTelemetry correctly to send it to an observability backend? This is what this blog post is all about. And you’ll also learn how to send custom attributes by leveraging OpenTelemetry SDKs.&lt;/p&gt;

&lt;h2&gt;
  
  
  .NET Aspire
&lt;/h2&gt;

&lt;p&gt;.NET Aspire was first announced and introduced at &lt;a href="https://www.youtube.com/watch?v=mna5fg7QGz8&amp;amp;t=2655s"&gt;.NET Conf 2023 Keynote&lt;/a&gt;. The challenges it tries to solve are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Complex&lt;/strong&gt;: Cloud computing is fundamentally hard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Getting started&lt;/strong&gt;: For new developers in this space, a first step into cloud native can be overwhelming.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choices&lt;/strong&gt;: Developers need to make a lot of choices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paved path&lt;/strong&gt;: .NET did not have a golden paved path available for developers to build cloud-native applications.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This is exactly where .NET Aspire comes into play. It includes the following features as part of the stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9f9gd6ebc415hj3dhr2s.png" alt="Image description" width="800" height="398"&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/components-overview"&gt;Components&lt;/a&gt;: Curated suite of NuGet packages specifically selected to facilitate the integration of cloud-native applications with prominent services and platforms, including but not limited to Redis and PostgreSQL. Each component furnishes essential cloud-native functionalities through either automatic provisioning or standardized configuration patterns.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdqc4w322gwii2ub7hgdd.png" alt="Image description" width="800" height="401"&gt;
&lt;a href="https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/dashboard"&gt;Developer Dashboard&lt;/a&gt;: Allows you to track closely various aspects of your application, including logs, traces, and environment configurations, all in real time. It’s purpose-built to enhance the local development experience, providing an insightful overview of your app’s state and structure.&lt;/li&gt;
&lt;li&gt;
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuu73qjpofmex0hzgqu5k.png" alt="Image description" width="800" height="422"&gt;
&lt;a href="https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview#why-net-aspire"&gt;Tooling/Orchestration&lt;/a&gt;: .NET Aspire includes project templates and tooling experiences for Visual Studio and the dotnet command-line interface (CLI) help you create and interact with .NET Aspire apps. It also provides features for running and connecting multi-project applications and their dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  eShop demo application
&lt;/h2&gt;

&lt;p&gt;A reference .NET application implementing an ecommerce website using a services-based architecture.&lt;/p&gt;

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

&lt;p&gt;In the latest version of the application, the source code is already updated to include .NET Aspire as part of the project. You can install the latest &lt;a href="https://github.com/dotnet/installer#installers-and-binaries"&gt;.NET 8 SDK&lt;/a&gt; and clone the repository. Additionally, you can run the following commands to install the Aspire workload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet workload update
dotnet workload &lt;span class="nb"&gt;install &lt;/span&gt;aspire
dotnet restore eShop.Web.slnf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have all the other prerequisites ready on your machine, you can run the application from your terminal with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet run &lt;span class="nt"&gt;--project&lt;/span&gt; src/eShop.AppHost/eShop.AppHost.csproj
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  .NET Aspire developer dashboard
&lt;/h2&gt;

&lt;p&gt;Once the application is up and running, go to the developer dashboard to identify the various resources that are part of the eShop application, including the endpoints and URL(s) to reach the running resources directly.&lt;/p&gt;

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

&lt;p&gt;This dashboard also includes monitoring telemetry, including logs, traces, and metrics.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  .NET Aspire orchestration
&lt;/h2&gt;

&lt;p&gt;.NET Aspire provides APIs for expressing resources and dependencies within your distributed application.&lt;/p&gt;

&lt;p&gt;Before continuing, consider some common terminology used in .NET Aspire:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;App model&lt;/strong&gt;: A collection of resources that make up your distributed application (&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/aspire.hosting.distributedapplication"&gt;DistributedApplication&lt;/a&gt;). For a more formal definition, see &lt;a href="https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/app-host-overview#define-the-app-model"&gt;Define the app model&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App host/Orchestrator project&lt;/strong&gt;: The .NET project that orchestrates the app model, named with the *.AppHost suffix (by convention).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource&lt;/strong&gt;: A &lt;a href="https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/app-host-overview#built-in-resource-types"&gt;resource&lt;/a&gt; represents a part of an application whether it be a .NET project, container, or executable, or some other resource like a database, cache, or cloud service (such as a storage service).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reference&lt;/strong&gt;: A reference defines a connection between resources, expressed as a dependency. For more information, see &lt;a href="https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/app-host-overview#reference-resources"&gt;Reference resources&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;.NET Aspire empowers you to seamlessly build, provision, deploy, configure, test, run, and observe your cloud application. This is achieved through the utilization of an app model that outlines the resources in your app and their relationships.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending telemetry to an OpenTelemetry backend such as New Relic
&lt;/h2&gt;

&lt;p&gt;Having a built-in dashboard for the monitoring data is nice during development. In this section I focus on how to configure OpenTelemetry correctly to send all telemetry into New Relic as my observability backend of choice.&lt;/p&gt;

&lt;p&gt;For the scenario described in this article, I created my own fork of the official eShop application. Within this &lt;a href="https://github.com/harrykimpel/dotnet-eShop"&gt;repository&lt;/a&gt;, you’ll be able to find the &lt;a href="https://github.com/harrykimpel/dotnet-eShop/tree/main/src/eShop.AppHost"&gt;app host project&lt;/a&gt; that contains its &lt;a href="https://github.com/harrykimpel/dotnet-eShop/blob/main/src/eShop.AppHost/Program.cs"&gt;main component&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Lines 17 through 26 define some basic configuration variables that you can provide using environment variables in your terminal.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NEW_RELIC_LICENSE_KEY&lt;/strong&gt;: New Relic license key for the OpenTelemetry protocol (OTLP) API header value&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NEW_RELIC_REGION&lt;/strong&gt;: US or EU region configuration for your New Relic account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Based on the New Relic region configuration, the code will define the &lt;a href="https://docs.newrelic.com/docs/more-integrations/open-source-telemetry-integrations/opentelemetry/get-started/opentelemetry-set-up-your-app/#review-settings?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q1-devtoupdates"&gt;New Relic OTLP endpoint for OpenTelemetry&lt;/a&gt; and use it in the &lt;strong&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/strong&gt; variable.&lt;/p&gt;

&lt;p&gt;The rest of the app host project is already prepared to add an environment configuration for each of the projects that are part of the Aspire application. For example, here’s the configuration for the &lt;a href="https://github.com/harrykimpel/dotnet-eShop/tree/main/src/Identity.API"&gt;Identity.API project&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="c1"&gt;// Services&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;identityApi&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Identity_API&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"identity-api"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;identityDb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OTEL_EXPORTER_OTLP_ENDPOINT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OTEL_EXPORTER_OTLP_HEADERS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OTEL_EXPORTER_OTLP_HEADERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OTEL_SERVICE_NAME"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"identity-api"&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;In this fork of the eShop application I’ve added some additional environment configuration. Each of the &lt;strong&gt;.WithEnvironment&lt;/strong&gt; statements adds a necessary environment variable for the service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/strong&gt;: The OTLP endpoint for all the telemetry for this service; in our case, the New Relic OTLP endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OTEL_EXPORTER_OTLP_HEADERS&lt;/strong&gt;: The API header value, which includes our New Relic license key (&lt;strong&gt;string OTEL_EXPORTER_OTLP_HEADERS = "api-key=" + NEW_RELIC_LICENSE_KEY;&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OTEL_SERVICE_NAME&lt;/strong&gt;: The name of the service relevant to create a respective entity in New Relic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rest of the services are configured appropriately.&lt;/p&gt;

&lt;p&gt;Once you’ve configured the environment variables in your terminal (that is, &lt;strong&gt;NEW_RELIC_LICENSE_KEY&lt;/strong&gt; and &lt;strong&gt;NEW_RELIC_REGION&lt;/strong&gt;), you can start the Aspire application with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet run &lt;span class="nt"&gt;--project&lt;/span&gt; src/eShop.AppHost/eShop.AppHost.csproj
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can confirm whether everything is configured correctly by looking at the environment and clicking on the view icon for one of the projects:&lt;/p&gt;

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

&lt;p&gt;The &lt;strong&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/strong&gt; should point to the New Relic OTLP endpoint.&lt;/p&gt;

&lt;p&gt;After a little while, you should be able to see data from your application visible in the &lt;strong&gt;APM &amp;amp; Services - OpenTelemetry&lt;/strong&gt; section of New Relic:&lt;/p&gt;

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

&lt;p&gt;You can then observe and analyze all your telemetry. For example, look at the &lt;strong&gt;New Relic Services map&lt;/strong&gt;:&lt;/p&gt;

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

&lt;p&gt;… or the distributed tracing view:&lt;/p&gt;

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

&lt;p&gt;Happy observing!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Integrating OpenTelemetry with the .NET Aspire eShop application and New Relic allows you to leverage powerful telemetry tools to monitor and improve your application's performance. This setup not only provides valuable insights but also enhances your ability to diagnose issues quickly and efficiently. With the steps outlined in this guide, you're well on your way to building a more resilient and observant application. Start harnessing the full potential of your telemetry data today and keep your systems running smoothly!&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explore more&lt;/strong&gt;: Dive deeper into &lt;a href="https://docs.newrelic.com/docs/more-integrations/open-source-telemetry-integrations/opentelemetry/opentelemetry-introduction/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q1-devtoupdates"&gt;New Relic’s OpenTelemetry documentation&lt;/a&gt; to unlock advanced features.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Join the community&lt;/strong&gt;: Engage with other developers on New Relic’s community forum.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stay updated&lt;/strong&gt;: Follow &lt;a href="https://newrelic.com/blog?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q1-devtoupdates"&gt;our blog&lt;/a&gt; for the latest tips, tutorials, and industry news.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try New Relic for free&lt;/strong&gt;: Sign up for a &lt;a href="https://www.newrelic.com/signup?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q1-devtoupdates"&gt;free New Relic account&lt;/a&gt; and start exploring how New Relic can enhance your application's telemetry today.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Experiment and iterate&lt;/strong&gt;: Continuously monitor, analyze, and improve your telemetry setup for peak performance.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>observability</category>
      <category>opensource</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>How to observe your Blazor WebAssembly application with OpenTelemetry and real user monitoring</title>
      <dc:creator>Harry Kimpel</dc:creator>
      <pubDate>Mon, 10 Jun 2024 08:01:46 +0000</pubDate>
      <link>https://forem.com/newrelic/how-to-observe-your-blazor-webassembly-application-with-opentelemetry-and-real-user-monitoring-kfn</link>
      <guid>https://forem.com/newrelic/how-to-observe-your-blazor-webassembly-application-with-opentelemetry-and-real-user-monitoring-kfn</guid>
      <description>&lt;p&gt;To read this full article, &lt;a href="https://newrelic.com/blog/how-to-relic/how-to-observe-your-blazor-webassembly-application-with-opentelemetry-and-real-user-monitoring?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q1-devtoupdates"&gt;click here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Observing WebAssembly applications presents unique challenges that stem from its design and execution environment. Unlike traditional web applications, where monitoring tools can hook directly into JavaScript and the Document Object Model (DOM), WebAssembly runs as binary code executed within the browser's sandbox. This layer of abstraction complicates direct introspection, as traditional monitoring tools are not designed to interact with the lower-level operations of WebAssembly. The &lt;a href="https://bytecodealliance.org/"&gt;Bytecode Alliance&lt;/a&gt; plays a crucial role here, promoting standards and tools that aim to enhance the security and usability of WebAssembly, including better support for observability. Moreover, the performance characteristics of WebAssembly, which can closely approach native speeds, demand monitoring solutions that are both highly efficient and minimally invasive to avoid impacting the user experience. This creates a complex scenario for developers who need detailed visibility into their applications' behavior without sacrificing performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blazor WebAssembly: Leveraging .NET in the browser
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/?view=aspnetcore-8.0"&gt;.NET Blazor WebAssembly&lt;/a&gt; is a cutting-edge framework that allows developers to build interactive client-side web UIs using .NET rather than JavaScript. By compiling C# code into WebAssembly, Blazor WebAssembly empowers developers to leverage the full-stack capabilities of .NET, utilizing the same language and libraries on both the server and client sides. This unique approach streamlines the development process and enables rich, responsive user experiences with significant performance benefits.&lt;/p&gt;

&lt;p&gt;With the release of .NET 8, Blazor WebAssembly has introduced new rendering modes that enhance flexibility and performance across diverse deployment scenarios. These modes include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static server rendering&lt;/strong&gt; (also called static server-side rendering or static SSR) to generate static HTML on the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactive server rendering&lt;/strong&gt; (also called interactive server-side rendering or interactive SSR) to generate interactive components with prerendering on the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactive WebAssembly rendering&lt;/strong&gt; (also called client-side rendering or CSR, which is always assumed to be interactive) to generate interactive components on the client with prerendering on the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactive auto (automatic) rendering&lt;/strong&gt; to initially use the server-side ASP.NET Core runtime for content rendering and interactivity. The .NET WebAssembly runtime on the client is used for subsequent rendering and interactivity after the Blazor bundle is downloaded and the WebAssembly runtime activates. Interactive auto rendering usually provides the fastest app startup experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The auto rendering mode in particular make Blazor WebAssembly an even more compelling choice for developers who are looking to build modern web applications using .NET technologies.&lt;/p&gt;

&lt;p&gt;This blog post focuses specifically on Blazor WebAssembly and explores its capabilities and practical applications in modern web development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enhancing Blazor WebAssembly observability with OpenTelemetry in .NET
&lt;/h2&gt;

&lt;p&gt;In this exploration of Blazor WebAssembly, we delve into how OpenTelemetry (sometimes referred to as OTel) can be integrated with .NET to provide comprehensive observability for these applications. OpenTelemetry, a set of APIs, libraries, agents, and instrumentation, allows developers to collect and export telemetry data such as traces, metrics, and logs to analyze the performance and health of applications. For .NET developers, the integration with OpenTelemetry is particularly seamless, as all telemetry for .NET is considered stable, ensuring reliability and robust support across various deployment scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exploring a sample Blazor WebAssembly application
&lt;/h2&gt;

&lt;p&gt;In this blog post, we'll be utilizing a sample application that embodies the principles of Blazor WebAssembly combined with OpenTelemetry. The application, which can be found in the &lt;a href="https://github.com/harrykimpel/dotnet-blazor-samples/tree/main/8.0/BlazorWebAssemblyStandaloneWithIdentity"&gt;GitHub repository&lt;/a&gt;, serves as an excellent example of a standalone Blazor WebAssembly application. This practical example will serve as the foundation for our discussion on implementing and observing OpenTelemetry in a .NET environment. By dissecting this application, we can better understand the interaction between Blazor’s client-side component as a Blazor WebAssembly application running in the browser and a Blazor WebAssembly backend application that the web frontend talks to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated and manual instrumentation of the backend
&lt;/h2&gt;

&lt;p&gt;When deploying OpenTelemetry in a Blazor WebAssembly application, automated instrumentation becomes a pivotal component, particularly when interfacing with the .NET backend. OpenTelemetry's .NET libraries offer out-of-the-box instrumentation for ASP.NET Core, which effortlessly captures telemetry data such as HTTP requests, database queries, and much more. This automated process simplifies the task of implementing comprehensive monitoring, as it requires minimal manual configuration and coding. Additionally, integrating OpenTelemetry into your project is as straightforward as adding the respective NuGet packages to your solution.&lt;/p&gt;

&lt;p&gt;For developers working with Blazor WebAssembly, this means enhanced visibility into the backend operations that power their applications. Automated instrumentation ensures that all relevant data transactions between the client and server are meticulously monitored, providing insights into performance metrics and potential bottlenecks. By leveraging this feature, developers can focus more on building features and less on the intricacies of setting up and maintaining observability infrastructure, making it easier to deliver high-performance, reliable applications.&lt;/p&gt;

&lt;p&gt;The project file for the .NET Blazor WebAssembly backend looks like this (you can find the full &lt;a href="https://github.com/harrykimpel/dotnet-blazor-samples/blob/main/8.0/BlazorWebAssemblyStandaloneWithIdentity/Backend/Backend.csproj"&gt;project file in the repository&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   &amp;lt;PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.8.0" /&amp;gt;
   &amp;lt;PackageReference Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" Version="1.0.0-beta.9" /&amp;gt;
   &amp;lt;PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.8.0" /&amp;gt;
   &amp;lt;PackageReference Include="OpenTelemetry" Version="1.8.0" /&amp;gt;
   &amp;lt;PackageReference Include="OpenTelemetry.AutoInstrumentation.Runtime.Native" Version="1.5.0" /&amp;gt;
   &amp;lt;PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.8.0" /&amp;gt;
   &amp;lt;PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.8.0" /&amp;gt;
   &amp;lt;PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.8.1" /&amp;gt;
   &amp;lt;PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.8.1" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;When running the Blazor backend with automated OpenTelemetry instrumentation and exporting the telemetry to the console, we can easily see the traces, metrics, and logs as part of the console output (here in VS Code terminal).&lt;/p&gt;

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

&lt;p&gt;But looking at telemetry such as traces, metrics, and logs in the console output is not really helpful. So, ideally, we want to export that data to an OpenTelemetry telemetry backend. I am of course using New Relic.&lt;/p&gt;

&lt;p&gt;In a typical OpenTelemetry fashion, I only need to provide a few environment variables when executing the application. I highlighted the most important ones in the below screenshot. You can find the full &lt;a href="https://github.com/harrykimpel/dotnet-blazor-samples/blob/main/8.0/BlazorWebAssemblyStandaloneWithIdentity/Backend/run.sh"&gt;run script in the repository&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Once the application is up and running, you find the application entity in APM &amp;amp; Services under the OpenTelemetry section. The screenshot below shows the &lt;strong&gt;Summary&lt;/strong&gt; view.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Distributed Tracing&lt;/strong&gt; view:&lt;/p&gt;

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

&lt;p&gt;View of a single trace (I highlighted the backend span to &lt;strong&gt;/roles&lt;/strong&gt; endpoint):&lt;/p&gt;

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

&lt;p&gt;When you look at the above span to the &lt;strong&gt;BlazorWASMBackend&lt;/strong&gt; service &lt;strong&gt;GET /roles&lt;/strong&gt;, you’ll notice that the total time spent in the backend span is 4.61s. We cannot really tell where exactly the time is spent. Out of the box, when using auto instrumentation, I don’t get further detail into what is actually happening in that method.&lt;/p&gt;

&lt;p&gt;In order for me to provide more details into that method, I’ll need to add some manual instrumentation into my source code by leveraging the OpenTelemetry SDK for .NET.&lt;/p&gt;

&lt;p&gt;In this case, I changed the original code of the&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;app.MapGet("/roles", …&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
 method from this original implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;           // Instantiate random number generator using system-supplied value as seed.
           var rand = new Random();
           int waitTime = rand.Next(500, 5000);

           // do some heavy lifting work
           Thread.Sleep(waitTime);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and added in some .NET-specific implementation of a custom OpenTelemetry span:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;           // Instantiate random number generator using system-supplied value as seed.
           var rand = new Random();
           int waitTime = rand.Next(500, 5000);


           using (var sleepActivity = DiagnosticsConfig.ActivitySource.StartActivity("RolesHeavyLiftingSleep"))
           {
               // do some heavy lifting work
               Thread.Sleep(waitTime);


               string waitMsg = string.Format(@"ChildActivty simulated wait ({0}ms)", waitTime);
               sleepActivity?.SetTag("simulatedWaitMsg", waitMsg);
               sleepActivity?.SetTag("simulatedWaitTimeMs", waitTime);
               DiagnosticsConfig.logger.LogInformation(eventId: 123, waitMsg);
           }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The using-block actually triggers a new span to be created with the name &lt;strong&gt;RolesHeavyLiftingSleep&lt;/strong&gt;. I save the activity into the &lt;strong&gt;sleepActivity&lt;/strong&gt; variable. You’ll further notice that I’m also adding some custom tags to that same activity by calling &lt;strong&gt;sleepActivity?.SetTag()&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We can see in the screenshot below what the result of that change looks like if we restart our application.&lt;/p&gt;

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

&lt;p&gt;The trace in the screenshot above allows me to drill deeper into the backend span by enabling the in-process spans. In here, we can see that there’s a &lt;strong&gt;RolesParentActivity&lt;/strong&gt;, followed by a &lt;strong&gt;RolesChildActivity&lt;/strong&gt;, and finally our &lt;strong&gt;RolesHeavyLiftingSleep&lt;/strong&gt; span from our manual instrumentation. This screenshot also shows a section of the attributes that are associated with that span. As you can see, the custom tags &lt;strong&gt;simulatedWaitTimeMs&lt;/strong&gt; and &lt;strong&gt;simulatedWaitMsg&lt;/strong&gt; are visible as well. These custom tags are potentially helpful when doing some root cause analysis or troubleshooting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instrumentation of the frontend
&lt;/h2&gt;

&lt;p&gt;Now that we’ve seen how the backend can be instrumented with OpenTelemetry, let’s focus on the frontend now, that is, the Blazor WebAssembly component.&lt;/p&gt;

&lt;p&gt;When we try to instrument the WebAssembly component with auto instrumentation for OpenTelemetry, the &lt;a href="https://github.com/harrykimpel/dotnet-blazor-samples/blob/main/8.0/BlazorWebAssemblyStandaloneWithIdentity/BlazorWasmAuth/BlazorWasmAuth.csproj"&gt;C# project file&lt;/a&gt; looks similar to the backend. This is actually the benefit of Blazow WebAssembly—that we can use C# not only for the backend, but also for the WebAssembly frontend.&lt;/p&gt;

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

&lt;p&gt;Let’s configure the console exporter again for our OpenTelemetry telemetry. Unsurprisingly, the output will be visible in the respective developer tools of our web browser.&lt;/p&gt;

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

&lt;p&gt;The screenshot above shows the actual UI of the Blazor WebAssembly frontend in the upper section of the screenshot. If the user of the application clicks the &lt;strong&gt;Click me&lt;/strong&gt; button, a trace is generated and output in the browsers console view.&lt;/p&gt;

&lt;p&gt;Again, this is adequate for testing, but in order to actually leverage the telemetry in a meaningful way, we need to export all telemetry to an OpenTelemetry backend, in my case New Relic. For this to happen, we need to define an OpenTelemetry protocol (OTLP) exporter and configure the OTLP endpoint as well as the OTLP header information similar to what we’ve seen for the backend.&lt;/p&gt;

&lt;p&gt;But wait; as soon as we implement these changes and rerun the application, an exception is generated.&lt;/p&gt;

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

&lt;p&gt;The exception is &lt;strong&gt;Operation is not supported on this platform&lt;/strong&gt;. If we think about it, it does make sense since we’re trying to make a HTTP request from our WebAssembly component to an external endpoint. However, since WebAssembly by itself doesn't have any access to its host environment, it doesn't have any built-in input/output (I/O) capabilities. For security reasons, it’s not possible to make HTTP requests from within a WebAssembly component.&lt;/p&gt;

&lt;p&gt;So, if plain OpenTelemetry instrumentation isn’t possible, what is it that we can do for the frontend then?&lt;/p&gt;

&lt;p&gt;As part of OpenTelemetry, the community is also working on some real user monitoring capabilities.&lt;/p&gt;

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

&lt;p&gt;However, this is very early stages and not fully spec’d out. The current draft is also only focusing on Node.js and TypeScript. In the future this may be an option that could be leveraged for the frontend component.&lt;/p&gt;

&lt;p&gt;One way to get details of the WebAssembly component is to leverage real user monitoring capabilities via the New Relic browser.&lt;/p&gt;

&lt;p&gt;After getting the JavaScript snippet from a newly created New Relic browser application copied into the Blazor WebAssembly project (&lt;a href="https://github.com/harrykimpel/dotnet-blazor-samples/blob/main/8.0/BlazorWebAssemblyStandaloneWithIdentity/BlazorWasmAuth/wwwroot/newrelic.js"&gt;this&lt;/a&gt; is the place to put it), we can already see some high-level telemetry from the frontend.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Distributed tracing&lt;/strong&gt; view:&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;AJAX&lt;/strong&gt; requests:&lt;/p&gt;

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

&lt;p&gt;What else can we do with the frontend? Some parts of the frontend will have interactions with the backend (like login and logout authentication). Other parts just execute code within the WebAssembly component. An example of this is the &lt;strong&gt;Counter&lt;/strong&gt; section.&lt;/p&gt;

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

&lt;p&gt;As you can see in the page source of that page, there’s no actual HTML representation.&lt;/p&gt;

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

&lt;p&gt;Let’s see what we can do there.&lt;/p&gt;

&lt;p&gt;One way is to invoke JavaScript functions from the actual .NET code. The following screenshot shows how this can be achieved (here is the link to the &lt;strong&gt;respective file&lt;/strong&gt; in the repository).&lt;/p&gt;

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

&lt;p&gt;The New Relic browser API allows users to add some custom page actions. This way we can observe all clicks on the counter and also capture the current value of the counter as a custom attribute.&lt;/p&gt;

&lt;p&gt;Once we’ve implemented this and deployed a new release of the application, we can for example show this data on a custom dashboard to see the distribution of the actual counter values across all users of the application.&lt;/p&gt;

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

&lt;p&gt;Furthermore, New Relic quickstarts, also known as Instant Observability, contain a sample dashboard that you can deploy into your account in order to see some additional Blazor WebAssembly specific telemetry in a pre-built dashboard.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Observing Blazor WebAssembly applications is not quite straightforward as of today. There are many moving parts that the industry and the respective open-source communities are working on. This applies to the WebAssembly component model, as well as the OpenTelemetry implementation of real user monitoring. I think these challenges will soon be solved and there will be easier ways to get started.&lt;/p&gt;

&lt;p&gt;Until then, in this blog post I showed a way to get some insights into your Blazor WebAssembly backend and frontend components.&lt;/p&gt;

&lt;p&gt;New Relic provides a &lt;a href="https://newrelic.com/signup?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q1-devtoupdates"&gt;free account&lt;/a&gt; that you can use to get started with your journey on observing Blazor WebAssembly applications.&lt;/p&gt;

</description>
      <category>observability</category>
      <category>opentelemetry</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Guide: How to route Docker logs correctly in New Relic</title>
      <dc:creator>Harry Kimpel</dc:creator>
      <pubDate>Fri, 12 Apr 2024 05:36:57 +0000</pubDate>
      <link>https://forem.com/newrelic/guide-how-to-route-docker-logs-correctly-in-new-relic-iid</link>
      <guid>https://forem.com/newrelic/guide-how-to-route-docker-logs-correctly-in-new-relic-iid</guid>
      <description>&lt;p&gt;To read this full article, &lt;a href="https://newrelic.com/blog/how-to-relic/guide-how-to-route-docker-logs-correctly-in-new-relic?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q1-devtoupdates" rel="noopener noreferrer"&gt;click here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Streamlining Container Log Management for Clarity and Control&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hello, New Relic aficionados! Picture this: you're at a bustling local user group meetup, exchanging ideas and sharing tech stories. Amidst the animated discussions and clinking coffee cups, a fellow developer—let’s call him Alex—shares a frustrating puzzle. Alex’s Docker Compose applications are acting like rebellious teenagers, sending their logs to the New Relic Host UI instead of their designated New Relic Container UI. As you dive deeper into the problem, a light bulb goes off. This isn't just Alex's struggle; it’s a common snag affecting many of us in the Docker and New Relic community.&lt;/p&gt;

&lt;p&gt;Why do these logs prefer the scenic route, and how can we guide them to their proper home? Inspired by this real-life challenge, I embarked on a quest to demystify the log misdirection issue and share a roadmap to log management nirvana with New Relic. So, grab your digital compasses, dear readers—it's time to navigate through the foggy waters of Docker logging and ensure your data logs exactly where it should.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the core issue: Where do my container logs end up?
&lt;/h2&gt;

&lt;p&gt;So, what's the fuss about where logs end up anyway? Let's break it down. When you're running applications in containers, especially when using Docker, logging isn't just about keeping a record; it's about clarity and context. The &lt;a href="https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/get-started/install-infrastructure-agent/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q1-devtoupdates" rel="noopener noreferrer"&gt;New Relic infrastructure agent&lt;/a&gt; (along with the &lt;a href="https://github.com/newrelic/nri-docker" rel="noopener noreferrer"&gt;New Relic integration for Docker&lt;/a&gt;) is designed to be thorough—it dutifully collects logs from the host and any containers running on it. But here’s the snag: instead of neatly categorizing container logs under each container, all logs get lumped together. Your container logs are showing up right alongside host/server logs in the &lt;a href="https://docs.newrelic.com/docs/infrastructure/infrastructure-ui-pages/infra-hosts-ui-page/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q1-devtoupdates" rel="noopener noreferrer"&gt;New Relic infrastructure Hosts UI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.kimpel.com%2F20240411-container-logs-1.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%2Fcdn.kimpel.com%2F20240411-container-logs-1.png" alt="hosts ui container logs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the Logs section of your Container UI does not show any logs:&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%2Fcdn.kimpel.com%2F20240411-container-logs-2.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%2Fcdn.kimpel.com%2F20240411-container-logs-2.png" alt="container ui no logs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why does this matter? Imagine trying to find a specific conversation in a bustling, crowded room. Every discussion, whether it’s crucial or trivial, merges into one overwhelming cacophony. That's your current logging scenario—container logs are getting lost in the noise of the host logs, making it challenging to pinpoint issues or understand the behavior of specific containers.&lt;/p&gt;

&lt;p&gt;This misassignment doesn’t just clutter your view; it complicates monitoring and troubleshooting by obscuring the boundaries between container-specific operations and overall host activity. You need to see your container logs isolated from host logs to quickly diagnose issues, scale effectively, and understand exactly how your containers are performing in the wild.&lt;/p&gt;

&lt;p&gt;In the following sections, we'll explore why this log misplacement occurs and how you can reroute your logs to their correct destinations in New Relic, ensuring that your monitoring setup is as sharp and efficient as your development environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring your environment for precise log routing
&lt;/h2&gt;

&lt;p&gt;Now that we understand the issue at hand, let's roll up our sleeves and tackle the solution. Organizing your logs with New Relic starts with a few key adjustments in your environment. Here’s a simple guide to ensure your container logs aren’t just collected but correctly associated with their respective container entities in New Relic Logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Adjust your logging configuration
&lt;/h3&gt;

&lt;p&gt;Chances are, this step might already be in place if you've been monitoring with New Relic. Your goal here is to ensure that the logs from each container are being captured effectively. This involves tweaking your &lt;a href="https://docs.newrelic.com/docs/logs/forward-logs/forward-your-logs-using-infrastructure-agent/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q1-devtoupdates" rel="noopener noreferrer"&gt;logging configurations&lt;/a&gt; to include detailed information about each container's operations. If this isn't set up yet, here's what you typically need to modify in your environment settings to start capturing those logs.&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%2Fcdn.kimpel.com%2F20240411-container-logs-3.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%2Fcdn.kimpel.com%2F20240411-container-logs-3.png" alt="container logs infra config"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s the snippet that you can copy and use in your logging.yml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;containers&lt;/span&gt;
    &lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/lib/docker/containers/*/*.log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Tagging containers in Docker
&lt;/h3&gt;

&lt;p&gt;Next, you’ll need to make your containers recognizable by tags when running your containers manually using ‘docker run’ command or within your Docker Compose configuration file. Adding the below &lt;a href="https://docs.docker.com/config/containers/logging/log_tags/" rel="noopener noreferrer"&gt;tag&lt;/a&gt; makes this happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker run command example
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--log-opt&lt;/span&gt; &lt;span class="nv"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{{.Name}}"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; log-generator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Docker Compose example in docker-compose.yml&lt;/li&gt;
&lt;/ul&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%2Fcdn.kimpel.com%2F20240411-container-logs-4.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%2Fcdn.kimpel.com%2F20240411-container-logs-4.png" alt="container logs docker compose tag"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s the snippet that you can copy and use in your &lt;strong&gt;docker-compose.yml&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.9'&lt;/span&gt;
&lt;span class="na"&gt;x-default-logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;logging&lt;/span&gt;
  &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json-file"&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5m"&lt;/span&gt;
    &lt;span class="na"&gt;max-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2"&lt;/span&gt;
    &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{.Name}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration ensures that each log entry from your containers includes the container's name as a tag, making it easier to track in the logs collected by New Relic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Set up a log parsing rule in New Relic Logs
&lt;/h3&gt;

&lt;p&gt;Once you start seeing the container tags coming through in your New Relic environment, it’s time to refine how these logs are parsed and displayed. &lt;a href="https://docs.newrelic.com/docs/logs/ui-data/parsing/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q1-devtoupdates" rel="noopener noreferrer"&gt;Setting up a log parsing rule&lt;/a&gt; helps in categorizing and querying logs based on specific attributes.&lt;/p&gt;

&lt;p&gt;Here’s how to configure it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt;: container name&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Field to parse&lt;/strong&gt;: attrs.tag&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter logs based on New Relic Query Language (NRQL)&lt;/strong&gt;: attrs.tag is not null&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parsing rule&lt;/strong&gt;: %{NOTSPACE:containerName}&lt;/li&gt;
&lt;/ul&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%2Fcdn.kimpel.com%2F20240411-container-logs-5.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%2Fcdn.kimpel.com%2F20240411-container-logs-5.png" alt="container logs parsing rule tag"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This parsing rule will extract the container name from the &lt;strong&gt;attrs.tag&lt;/strong&gt; field of each log entry and add an additional attribute named &lt;strong&gt;containerName&lt;/strong&gt;, enabling you to filter and analyze logs by specific containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With these three steps, your container logs will not only be more organized but also more insightful. By ensuring that each log entry is correctly tagged and parsed, they'll directly feed into New Relic's powerful logging tools, allowing you to monitor and troubleshoot with unmatched precision. This setup not only enhances your ability to respond to issues swiftly but also empowers your team with data-driven insights, ensuring your applications run smoothly and efficiently.&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%2Fcdn.kimpel.com%2F20240411-container-logs-6.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%2Fcdn.kimpel.com%2F20240411-container-logs-6.png" alt="container ui with logs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we wrap up our guide on streamlining Docker logs in New Relic, remember that effective log management is just one piece of the observability puzzle. To further illustrate the power of integrated monitoring tools, I recently contributed to enhancing the OpenTelemetry demo application. My pull request (&lt;a href="https://github.com/open-telemetry/opentelemetry-demo/pull/1495" rel="noopener noreferrer"&gt;PR #1495&lt;/a&gt;) has been accepted and merged, introducing a new tagging feature that you'll find particularly useful when &lt;a href="https://opentelemetry.io/docs/demo/docker-deployment/" rel="noopener noreferrer"&gt;running the demo with Docker Compose&lt;/a&gt;. This addition helps ensure that container logs are easily distinguishable and accurately attributed, enhancing your ability to monitor and troubleshoot effectively. Check out the changes and explore how they can benefit your setup &lt;a href="https://github.com/open-telemetry/opentelemetry-demo/pull/1495" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In addition to contributing to the OpenTelemetry demo application, I've also updated the respective sections of the New Relic documentation to reflect these improvements in log management. This update ensures that the strategies and technical details we've discussed are not only tested but also officially integrated into our guidance, making it easier for you to implement and benefit from these changes. You can view these updates (soon) to enhance your understanding and application of these practices, ensuring you get the most out of your New Relic tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Congratulations on configuring your Docker environment for optimal log management! But the journey doesn’t stop here. Dive deeper into the possibilities:&lt;/p&gt;

&lt;p&gt;Experiment and refine: Adjust the logging levels and parsing rules based on the specific needs of your environment. Each application may have unique insights to offer.&lt;br&gt;
Monitor the impact: Keep an eye on how these changes enhance your monitoring capabilities. Look for improvements in troubleshooting and system performance.&lt;br&gt;
Share your insights: Got a handle on things? Don’t keep it to yourself. Share your success stories and challenges in the comments below or on social media. Your experiences could shine a light for fellow developers navigating similar challenges.&lt;br&gt;
Engage with the community: Join discussions on the New Relic Explorers Hub to connect with other users and exchange tips and tricks. Your feedback not only contributes to community growth but also helps us improve and evolve our solutions.&lt;/p&gt;

&lt;p&gt;Ready to elevate &lt;a href="https://newrelic.com/signup?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy25-q1-devtoupdates" rel="noopener noreferrer"&gt;your log management game&lt;/a&gt;? Start tweaking, sharing, and engaging today. Let’s harness the full potential of precise log data together!&lt;/p&gt;

</description>
      <category>observability</category>
      <category>logs</category>
      <category>docker</category>
    </item>
    <item>
      <title>A deep dive into zero-day vulnerability alerts with New Relic APM</title>
      <dc:creator>Harry Kimpel</dc:creator>
      <pubDate>Fri, 23 Feb 2024 18:52:42 +0000</pubDate>
      <link>https://forem.com/newrelic/a-deep-dive-into-zero-day-vulnerability-alerts-with-new-relic-apm-29kb</link>
      <guid>https://forem.com/newrelic/a-deep-dive-into-zero-day-vulnerability-alerts-with-new-relic-apm-29kb</guid>
      <description>&lt;p&gt;To read this full article, &lt;a href="https://newrelic.com/blog/how-to-relic/a-deep-dive-into-zero-day-vulnerability-alerts-with-new-relic-apm?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;click here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Amidst the ever-evolving landscape of cybersecurity, the &lt;a href="https://www.securityweek.com/fortinet-warns-of-new-fortios-zero-day/"&gt;recent revelation&lt;/a&gt; of a &lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2024-21762"&gt;zero-day vulnerability in Fortinet's FortiOS&lt;/a&gt; serves as a stark reminder of the constant cat-and-mouse game between defenders and attackers.&lt;/p&gt;

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

&lt;p&gt;Staying ahead of potential security threats isn’t just a best practice; it's a necessity. For developers, the challenge lies not only in identifying vulnerabilities but in doing so proactively, especially when it comes to zero-day exploits. In this blog post, we'll explore how New Relic application performance monitoring (APM) empowers developers to create zero-day vulnerability alerts, offering a robust solution to enhance security postures without the need for extensive scanning.&lt;/p&gt;

&lt;p&gt;Developers are often tasked with managing the delicate balance between agility and security. New Relic recognizes this challenge and provides a comprehensive set of tools to streamline the process. Today, we'll delve into two key capabilities—alert conditions and policies within the New Relic platform and the integration with &lt;a href="https://docs.newrelic.com/docs/vulnerability-management/overview/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;Vulnerability Management&lt;/a&gt;—that enable developers to create targeted alert rules and effortlessly control their security posture.&lt;/p&gt;

&lt;p&gt;Let's embark on a journey through these capabilities, exploring how they equip developers to receive timely notifications on specific common vulnerabilities and exposures (CVEs) and maintain an up-to-date understanding of their application's security status. Additionally, we'll unravel the magic of the &lt;a href="https://docs.newrelic.com/docs/data-apis/get-started/nrdb-horsepower-under-hood/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;New Relic Database (NRDB)&lt;/a&gt; and the Environment Snapshot tab, where all changes, including library modifications, are meticulously recorded. Join us as we navigate the realm of zero-day vulnerability alerts, unlocking the full potential of &lt;a href="https://docs.newrelic.com/docs/apm/new-relic-apm/getting-started/introduction-apm/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;New Relic APM&lt;/a&gt; for developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What exactly are zero-day vulnerability alerts?
&lt;/h2&gt;

&lt;p&gt;Before we dive deeper into the advantages of zero-day alerts, let's understand what zero-day alerts are.&lt;/p&gt;

&lt;p&gt;Zero-day alerts are crucial in software development. Picture this: You're in the midst of coding, and suddenly, an alert pops up—a zero-day vulnerability is detected. It's not just any vulnerability; it's one that nobody knew about until now. Zero-day alerts are like unexpected guests—they demand immediate attention.&lt;/p&gt;

&lt;p&gt;These alerts signal the emergence of previously unknown vulnerabilities or security threats. Unlike known issues with patches, zero-day vulnerabilities are wild cards, demanding swift action and vigilance.&lt;/p&gt;

&lt;p&gt;Some developers may think that once their source code has been scanned for vulnerabilities at build time, their job is done. However, once the application or service is in production, on average up to three years later, a vulnerability exposure will be disclosed that was not known when the source code was originally scanned. Finding and fixing these vulnerabilities is often a crisis moment for many organizations.&lt;/p&gt;

&lt;p&gt;Zero-day alerts aren't just notifications; they're urgent calls to action. They remind us of the ever-changing digital landscape, urging us to stay vigilant and responsive in our defenses. They're about staying ahead, anticipating the unexpected, and protecting our digital realms from the unknown. In software development, they're the plot twists that keep us on our toes, ready to tackle surprises head-on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unveiling the developer advantages
&lt;/h2&gt;

&lt;p&gt;As developers, embracing a proactive security posture is not just a choice; it's a strategic advantage. &lt;a href="https://docs.newrelic.com/docs/apm/new-relic-apm/getting-started/introduction-apm/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;New Relic APM&lt;/a&gt; and &lt;a href="https://docs.newrelic.com/docs/vulnerability-management/overview/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;Vulnerability Management&lt;/a&gt; provide a dynamic duo that equips development teams with an array of benefits, revolutionizing the way we approach security in the software development lifecycle.&lt;/p&gt;

&lt;p&gt;Here are some benefits of this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Real-time alerts on zero-day events&lt;/strong&gt;:&lt;br&gt;
Traditional scanners operate on scheduled scans, often missing the critical moment when a zero-day library event occurs. With APM and Vulnerability Management, developers receive real-time alerts, ensuring swift responses to potential vulnerabilities. This capability surpasses the limitations of scanners, providing a level of immediacy crucial in today's fast-paced development landscape.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Broad visibility across your entire environment&lt;/strong&gt;:&lt;br&gt;
The APM agent acts as a vigilant sentinel, offering broad visibility across your entire environment. It goes beyond individual applications, providing insights into what's running, where it's running, and the security status across thousands of applications. This holistic perspective empowers developers with a comprehensive understanding of their application landscape, surpassing the capabilities of traditional scanning tools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Instant impact assessment&lt;/strong&gt;:&lt;br&gt;
Imagine having immediate answers to critical questions: Where are we affected? What is the impact? How do I fix it? APM and Vulnerability Management not only provide alerts but also enable developers to assess the impact instantly. Automation further streamlines the process, allowing for quick decision-making and efficient remediation. Developers can stay informed about the status of remediation efforts assigned to specific problems, fostering a culture of accountability and transparency.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Continuous zero-day analysis&lt;/strong&gt;:&lt;br&gt;
While scanners offer point-in-time snapshots that may become outdated with changes to code bases or environments, APM and Vulnerability Management provide continuous analysis. Every change in your environment is captured and assessed in real time. This ensures that your security posture isn’t just a momentary snapshot but an ongoing, adaptive process that evolves with your application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Proactive prevention and collaboration&lt;/strong&gt;:&lt;br&gt;
APM and Vulnerability Management go beyond mere detection; they empower developers to proactively prevent issues. Receive notifications to avoid calling a specific library due to the need for an upgrade, preventing the creation of a stack of baseline library vulnerabilities associated with multiple entities. This proactive approach not only mitigates risks but also minimizes the high cost of addressing vulnerabilities down the road.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automatic detection and fixing of new dependencies&lt;/strong&gt;:&lt;br&gt;
Stay ahead of the curve by automatically detecting and fixing vulnerabilities in new dependencies. APM and Vulnerability Management enable developers to address issues before they propagate, often resolving vulnerabilities before the security team is even aware of the issue. This level of automation not only enhances security but also optimizes development workflows, allowing teams to focus on innovation rather than firefighting.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, armed with the knowledge of these substantial benefits, let's explore the practical steps to harness these capabilities and create zero-day vulnerability alerts using &lt;a href="](https://docs.newrelic.com/docs/apm/new-relic-apm/getting-started/introduction-apm/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates)"&gt;New Relic APM&lt;/a&gt;, &lt;a href="https://docs.newrelic.com/docs/alerts-applied-intelligence/overview/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;alerts and applied intelligence&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating targeted alert rules with New Relic
&lt;/h2&gt;

&lt;p&gt;As developers, vigilance is key when it comes to security, and New Relic APM provides a powerful ally in this pursuit. With the alerts and applied intelligence capabilities, developers can seamlessly create tailored alert rules to receive timely notifications on specific CVEs.&lt;/p&gt;

&lt;p&gt;Imagine a scenario where a critical CVE is identified, and swift action is necessary. New Relic APM allows you to navigate to the Alert Conditions tab, where you can set up customized conditions based on specific parameters such as error rates, response times, or throughput. By integrating Vulnerability Management into this process, you can extend your alert rules to cover vulnerabilities, making your security response not just rapid but also finely tuned to the unique characteristics of your application.&lt;/p&gt;

&lt;p&gt;Let's break it down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Alerts &amp;amp; AI&lt;/strong&gt; in the New Relic platform:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Locate the &lt;strong&gt;Alert Policies&lt;/strong&gt; section to access the powerful alerting capabilities of the New Relic platform.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Create a New Alert policy:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Define a new alert policy tailored to your application's needs. This policy will serve as the foundation for your customized alert rules.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Create an alert condition&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Provide a name &lt;strong&gt;“New Zero Day Library Vulnerability”&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enter the New Relic Query Language (NRQL) query&lt;br&gt;
&lt;br&gt;
&lt;code&gt;SELECT count(*) FROM Vulnerability where issueType = 'Library Vulnerability'&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqnwls7yjtfo7s50m2f4l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqnwls7yjtfo7s50m2f4l.png" alt="Image description" width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Define thresholds. The important aspect in this section is &lt;strong&gt;“Open incidents with a:”&lt;/strong&gt;. Here you’ll specify when to trigger a critical incident. Of course, we want to get alerted as soon as the query returns a value above 0 at least once in the given time window.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7fpn5u6dstwox35zhx6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7fpn5u6dstwox35zhx6.png" alt="Image description" width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add details. Provide a name for your alert condition and adjust the other settings as you see fit.&lt;/p&gt;

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

&lt;p&gt;By following these steps, you empower your development team with a proactive stance against vulnerabilities, receiving notifications that are not just timely but also precisely aligned with your application's unique security requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Next, let's explore how New Relic goes beyond alerting by providing a comprehensive record of all changes, ensuring a thorough understanding of your application's security landscape.&lt;/p&gt;

&lt;p&gt;To make it even easier for you to get started, I’ve created a &lt;a href="https://github.com/harrykimpel/o11y-as-code"&gt;GitHub repository&lt;/a&gt; that contains a &lt;a href="https://github.com/harrykimpel/o11y-as-code/blob/main/zero-day-vulnerabilities/zero-day-vulnerability-alert.tf"&gt;Terraform script&lt;/a&gt; to create a sample alert policy and condition using the above concept. Alternatively, it also contains a &lt;a href="https://github.com/harrykimpel/o11y-as-code/blob/main/zero-day-vulnerabilities/zero-day-vulnerability-alert.gql"&gt;NerdGraph query&lt;/a&gt; (New Relic's GraphQL API) that you can use along with &lt;a href="https://api.newrelic.com/graphiql?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;New Relic’s New Relic's Graphiql Explorer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Get started with your &lt;a href="https://newrelic.com/signup?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;free New Relic account today&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devsecops</category>
      <category>observability</category>
      <category>apm</category>
      <category>vulnerabilities</category>
    </item>
    <item>
      <title>How to monitor Microsoft 365: Observing AD FS</title>
      <dc:creator>Harry Kimpel</dc:creator>
      <pubDate>Sun, 26 Nov 2023 23:00:00 +0000</pubDate>
      <link>https://forem.com/newrelic/how-to-monitor-microsoft-365-observing-ad-fs-14me</link>
      <guid>https://forem.com/newrelic/how-to-monitor-microsoft-365-observing-ad-fs-14me</guid>
      <description>&lt;p&gt;To read this full article, &lt;a href="https://newrelic.com/blog/how-to-relic/how-to-monitor-microsoft-365-observing-ad-fs?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;click here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;A practical guide to Active Directory Federation Services for a resilient Microsoft 365 ecosystem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In our &lt;a href="https://newrelic.com/blog/how-to-relic/how-to-monitor-microsoft-365-getting-started?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;previous blog on how to monitor Microsoft 365&lt;/a&gt; (M365), we delved into service overviews and the critical importance of synthetic user login monitoring. In this blog, we set our sights on a core component that forms the backbone of secure identity and access management: &lt;a href="https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/ad-fs-overview"&gt;Active Directory Federation Services&lt;/a&gt; (AD FS).&lt;/p&gt;

&lt;p&gt;As organizations increasingly migrate their operations to the cloud, ensuring the robustness of identity and authentication mechanisms becomes paramount. AD FS plays a pivotal role in this landscape, acting as the linchpin for seamless and secure single sign-on (SSO) experiences within the M365 ecosystem.&lt;/p&gt;

&lt;p&gt;In this installment, we aim to equip you with the knowledge and tools needed to ensure the reliability and security of your AD FS implementation: navigate the terrain of certificate validations and metadata exchange documents, and unravel key elements that warrant vigilant oversight in your M365 environment Let's dive into the world of AD FS and uncover the essentials of effective monitoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate AD FS certificates
&lt;/h2&gt;

&lt;p&gt;Validating &lt;a href="https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/design/certificate-requirements-for-federation-servers"&gt;AD FS certificates&lt;/a&gt; is a crucial aspect of maintaining a secure and reliable authentication infrastructure within M365. Certificates serve as cryptographic keys that facilitate secure communication between different components of the AD FS environment. Regular validation ensures that these certificates are not only genuine but also up to date, reducing the risk of unauthorized access or security breaches. An expired or compromised certificate can lead to service disruptions, hindering the seamless flow of authentication requests. By enforcing rigorous certificate validation practices, organizations can fortify their AD FS implementation, enhance overall security, and provide users with a consistent and trustworthy SSO experience.&lt;/p&gt;

&lt;p&gt;Follow the steps below to get started with monitoring your AD FS certificates.&lt;/p&gt;

&lt;p&gt;AD FS monitoring is implemented as an &lt;a href="https://docs.newrelic.com/docs/infrastructure/host-integrations/get-started/introduction-host-integrations/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;on-host integration&lt;/a&gt; for the &lt;a href="https://docs.newrelic.com/docs/infrastructure/infrastructure-monitoring/get-started/get-started-infrastructure-monitoring/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;New Relic infrastructure&lt;/a&gt; agent. All of the configuration and necessary scripts are provided in a dedicated &lt;a href="https://github.com/harrykimpel/newrelic-microsoft-observability"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This integration will typically run on the same server that hosts the AD FS role. In order to get the integration deployed, follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install the New Relic infrastructure agent (if you need assistance, follow the &lt;a href="https://docs.newrelic.com/docs/infrastructure/host-integrations/installation/new-relic-guided-install-overview/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;guided install&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy the configuration file &lt;a href="https://github.com/harrykimpel/newrelic-microsoft-observability/blob/main/active-directory-federation-services/adfs-certificates/integrations.d/adfs-cert.yml"&gt;adfs-cert.yml&lt;/a&gt; and the PowerShell script &lt;a href="https://github.com/harrykimpel/newrelic-microsoft-observability/blob/main/active-directory-federation-services/adfs-certificates/integrations.d/GetExpiringCertificates.ps1"&gt;GetExpiringCertificates.ps1&lt;/a&gt; into the agent’s integration folder. These are the default locations:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Linux&lt;/strong&gt;: /etc/newrelic-infra/integrations.d/&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows&lt;/strong&gt;: C:\Program Files\New Relic\newrelic-infra\integrations.d&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Restart the infrastructure agent service.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The configuration file is straightforward and looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;integrations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nri-flex&lt;/span&gt;
   &lt;span class="s"&gt;interval&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;24h&lt;/span&gt;
   &lt;span class="s"&gt;config&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;M365AdfsCertificate&lt;/span&gt;
     &lt;span class="na"&gt;apis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;event_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;M365AdfsCertificate&lt;/span&gt;
         &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;powershell&lt;/span&gt;
         &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;299000&lt;/span&gt;
         &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
           &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"C:/Program&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Files/New&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Relic/newrelic-infra/integrations.d/GetExpiringCertificates.ps1"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Line 7 in the above configuration defines the name of the event where the data from this integration is stored in New Relic. We instruct the infrastructure agent to leverage the &lt;a href="https://docs.newrelic.com/docs/infrastructure/host-integrations/host-integrations-list/flex-integration-tool-build-your-own-integration/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;Flex&lt;/a&gt; integration (line 2) to leverage a PowerShell shell (line 8) in order to call the script defined in the run command (line 11).&lt;/p&gt;

&lt;p&gt;The actual PowerShell script looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$expiring_certs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cert:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExpiringInDays&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;365&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Issuer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;NotBefore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;NotAfter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FriendlyName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SerialNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Thumbprint&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Build an empty array to add our results to&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@()&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$StartDate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$expiring_certs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;

 &lt;/span&gt;&lt;span class="nv"&gt;$ts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-TimeSpan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$StartDate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-End&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NotAfter&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nv"&gt;$tsDaysReverse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Days&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="c"&gt;# Build a custom object to pass into the results&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nv"&gt;$cert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PSCustomObject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]@{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nx"&gt;certSubject&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Subject&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nx"&gt;certIssuer&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Issuer&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nx"&gt;certSerialNumber&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SerialNumber&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nx"&gt;certNotBefore&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DateTimeOffset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NotBefore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;ToUnixTimeSeconds&lt;/span&gt;&lt;span class="err"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nx"&gt;certNotAfter&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DateTimeOffset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NotAfter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;ToUnixTimeSeconds&lt;/span&gt;&lt;span class="err"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nx"&gt;certThumbprint&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Thumbprint&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nx"&gt;certExpiringIn&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ts&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nx"&gt;certExpiringInReverseDays&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$tsDaysReverse&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nx"&gt;certExpirationDate&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NotAfter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Uformat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nx"&gt;certFriendlyName&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FriendlyName&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

 &lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cert&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertTo-Json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first thing the script does is leverage the &lt;strong&gt;Get-ChildItem&lt;/strong&gt; module to get all certificates that are expiring in the next 365 days. Next, we construct an empty array that will be returned at the end. In a loop, we create new objects for each certificate found and add it including all the details to the results array. The final array will be converted into JSON and returned as output of the script.&lt;/p&gt;

&lt;p&gt;In the New Relic UI, we use the entity explorer to look at all the raw data that’s being collected.&lt;/p&gt;

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

&lt;p&gt;We can also build a custom dashboard to visualize the data in a meaningful way.&lt;/p&gt;

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

&lt;p&gt;Although we can refer to the data and dashboard this isn’t something that I want to manually check from time to time. Ideally, I want to get an alert notification if, for example, there’s a certificate about to expire in the next 30 days. This would probably give me enough time to renew a certificate or create a new one. With New Relic, this can easily be done by setting up an alert condition using a &lt;a href="https://docs.newrelic.com/docs/query-your-data/nrql-new-relic-query-language/get-started/introduction-nrql-new-relics-query-language/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;New Relic Query Language&lt;/a&gt; (NRQL) query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;certExpiringIn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Days&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="s1"&gt;'Cert expiring'&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;M365AdfsCertificate&lt;/span&gt; &lt;span class="n"&gt;facet&lt;/span&gt; &lt;span class="n"&gt;certSubject&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the threshold configuration, I can specify to trigger an incident whenever that query returns a value below 30.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Availability of the metadata exchange document
&lt;/h2&gt;

&lt;p&gt;Ensuring the availability of the &lt;a href="https://adfshelp.microsoft.com/MetadataExplorer/GetFederationMetadata"&gt;metadata exchange document&lt;/a&gt; is paramount for maintaining a resilient AD FS infrastructure within the M365 environment. The metadata exchange document contains critical information about AD FS endpoints, certificates, and other key metadata necessary for secure communication and authentication. Regularly checking its availability is essential to guarantee that this vital information is readily accessible to federation partners and other components in the ecosystem. An unavailable metadata exchange document can disrupt the federation process, leading to authentication failures and potential service outages. Proactively monitoring its availability allows organizations to identify and address issues promptly, ensuring the uninterrupted flow of authentication data and contributing to a robust and reliable M365 experience for users.&lt;/p&gt;

&lt;p&gt;Follow the steps below to get started with monitoring your metadata exchange document.&lt;/p&gt;

&lt;p&gt;AD FS monitoring is implemented as an &lt;a href="https://docs.newrelic.com/docs/infrastructure/host-integrations/get-started/introduction-host-integrations/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;on-host integration&lt;/a&gt; for the &lt;a href="https://docs.newrelic.com/docs/infrastructure/infrastructure-monitoring/get-started/get-started-infrastructure-monitoring/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;New Relic infrastructure&lt;/a&gt; agent. This integration will typically run on the same server that hosts the AD FS role. To deploy this integration, follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install the New Relic infrastructure agent (if you need assistance, follow the &lt;a href="https://docs.newrelic.com/docs/infrastructure/host-integrations/installation/new-relic-guided-install-overview/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;guided install&lt;/a&gt;). Note: If you already followed the steps described above on validating certificates, you can skip this step and start with step 2.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy the configuration file &lt;a href="https://github.com/harrykimpel/newrelic-microsoft-observability/blob/main/active-directory-federation-services/adfs-metadata-xml/integrations.d/adfs-metadata-xml.yml"&gt;adfs-metadata-xml.yml&lt;/a&gt; and the PowerShell script &lt;a href="https://github.com/harrykimpel/newrelic-microsoft-observability/blob/main/active-directory-federation-services/adfs-metadata-xml/integrations.d/GetMetadataXML.ps1"&gt;GetMetadataXML.ps1&lt;/a&gt; into the agent’s integration folder. These are the default locations:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Linux&lt;/strong&gt;: /etc/newrelic-infra/integrations.d/&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows&lt;/strong&gt;: C:\Program Files\New Relic\newrelic-infra\integrations.d&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Restart the infrastructure agent service&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Again, the configuration file is straightforward and looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;integrations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nri-flex&lt;/span&gt;
   &lt;span class="s"&gt;interval&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;60m&lt;/span&gt;
   &lt;span class="s"&gt;config&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;M365AdfsMetadata&lt;/span&gt;
     &lt;span class="na"&gt;apis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;event_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;M365AdfsMetadata&lt;/span&gt;
         &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;powershell&lt;/span&gt;
         &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;299000&lt;/span&gt;
         &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
           &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"C:/Program&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Files/New&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Relic/newrelic-infra/integrations.d/GetMetadataXML.ps1"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Line 7 in the above configuration defines the name of the event where the data from this integration is stored in New Relic. We instruct the infrastructure agent to leverage the &lt;a href="https://docs.newrelic.com/docs/infrastructure/host-integrations/host-integrations-list/flex-integration-tool-build-your-own-integration/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;Flex&lt;/a&gt; integration (line 2) to leverage a PowerShell shell (line 8) in order to call the script defined in the run command (line 11).&lt;/p&gt;

&lt;p&gt;The PowerShell script looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;add-type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sh"&gt;@"
   using System.Net;
   using System.Security.Cryptography.X509Certificates;
   public class TrustAllCertsPolicy : ICertificatePolicy {
       public bool CheckValidationResult(
           ServicePoint srvPoint, X509Certificate certificate,
           WebRequest request, int certificateProblem) {
           return true;
       }
   }
"@&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.Net.ServicePointManager&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;CertificatePolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TrustAllCertsPolicy&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$metadataUrl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://localhost/FederationMetadata/2007-06/FederationMetadata.xml"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$metadataUrl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-UseBasicParsing&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Build a custom object to pass into the results&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$jsonResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PSCustomObject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]@{&lt;/span&gt;&lt;span class="w"&gt;

   &lt;/span&gt;&lt;span class="nx"&gt;metadataXMLURL&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$metadataUrl&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusCode&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nx"&gt;statusDescription&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusDescription&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nx"&gt;rawContentLength&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RawContentLength&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$jsonResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertTo-Json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In line 14 we define the URL of the metadata exchange document which we then pass into the Invoke-WebRequest function (line 15). Next, we analyze the result and create an object with some details about the metadata exchange document, including the results from the web request; that is, whether or not the request was successful.&lt;/p&gt;

&lt;p&gt;In the New Relic UI, we use the entity explorer to look at all the raw data that’s being collected.&lt;/p&gt;

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

&lt;p&gt;We can also build a custom dashboard to visualize the data in a meaningful way.&lt;/p&gt;

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

&lt;p&gt;As we’ve seen in the previous example with expiring certificates, I want to take the proactive route and have New Relic alert me whenever a metadata exchange document is no longer available. With New Relic, this can easily be done by setting up an alert condition using an &lt;a href="https://docs.newrelic.com/docs/query-your-data/nrql-new-relic-query-language/get-started/introduction-nrql-new-relics-query-language/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;NRQL&lt;/a&gt; query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;M365AdfsMetadata&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;metadataXMLURL&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="n"&gt;facet&lt;/span&gt; &lt;span class="n"&gt;metadataXMLURL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above query returns the latest status code for each of the metadata exchange documents that I’m monitoring. If for any of these paths a status code of 400 or above occurs, I want to get an incident triggered.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Now that we've unraveled the intricacies of AD FS monitoring, it's time to empower your organization with a robust solution. By following the steps outlined in this blog, you can enhance your monitoring capabilities and fortify your Microsoft 365 environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Take the proactive step towards a more secure and reliable Microsoft 365 experience. Create your &lt;a href="https://newrelic.com/signup?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q4-devtoupdates"&gt;free New Relic account&lt;/a&gt; today and unlock a new era of AD FS monitoring excellence.&lt;/p&gt;

</description>
      <category>observability</category>
      <category>microsoft365</category>
      <category>adfs</category>
    </item>
    <item>
      <title>How to use CodeStream—and shift left your observability practice</title>
      <dc:creator>Harry Kimpel</dc:creator>
      <pubDate>Tue, 31 Oct 2023 19:00:00 +0000</pubDate>
      <link>https://forem.com/newrelic/how-to-use-codestream-and-shift-left-your-observability-practice-10cp</link>
      <guid>https://forem.com/newrelic/how-to-use-codestream-and-shift-left-your-observability-practice-10cp</guid>
      <description>&lt;p&gt;To read this full article, &lt;a href="https://newrelic.com/blog/how-to-relic/how-to-use-codestream-and-shift-left-your-observability-practice?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q3-devtoupdates"&gt;click here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Your adventure in the world of observability, performance optimization, and security begins here.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the fast-paced world of software development, the quest for smoother, more efficient applications feels like a never-ending adventure. But in this adventure, it's not about slaying dragons or uncovering hidden treasures; it's about ensuring your code performs seamlessly and your users are happy.&lt;/p&gt;

&lt;p&gt;As a developer, I've faced the challenges of reactive observability, where we typically identify and resolve issues only after they've disrupted our applications. It's a scenario that's all too familiar for many of us. Adding New Relic CodeStream to your tool inventory can help rescue your team from a perilous fate and bring their observability game to a new level.&lt;/p&gt;

&lt;p&gt;Picture this: you're deep into a new project, and your code is flowing like water. You're in your element, coding away. But at the back of your mind there's always that nagging concern: Will this code run smoothly in the wild, or are there lurking issues you've yet to uncover?&lt;/p&gt;

&lt;p&gt;Enter &lt;a href="https://newrelic.com/codestream?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q3-devtoupdates"&gt;New Relic CodeStream&lt;/a&gt;, a game-changer that's become my trusty companion in the world of software development. What's different about this tool? It's about shifting observability left in your development journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the shift-left approach
&lt;/h2&gt;

&lt;p&gt;Before we dive into how New Relic CodeStream can help you shift left with observability, it's important to understand the concept of shifting left itself. Traditionally, observability has been associated with monitoring and debugging in production. The shift-left approach encourages you to bring observability into the development and testing phases of your software development process. This proactive approach allows you to identify and address performance and reliability issues early in the development cycle, reducing the risk of deploying problematic code.&lt;/p&gt;

&lt;h2&gt;
  
  
  A game changer for shifting left
&lt;/h2&gt;

&lt;p&gt;New Relic CodeStream is a developer-friendly observability platform designed to seamlessly integrate observability into your existing workflows. It allows developers to collaborate, visualize, and diagnose issues within their preferred development tools.&lt;/p&gt;

&lt;p&gt;In this blog, I’m using a fork of &lt;a href="https://github.com/harrykimpel/data-driven-dave"&gt;a replica of the 1988 DOS game Dangerous Dave&lt;/a&gt;. This provides a fun way for me to showcase my adventure. And, who doesn’t like to play a good game from time to time. ;-) The purpose of this game is to collect as many items as possible in order to increase my score and finish as many levels as possible.&lt;/p&gt;

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

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

&lt;p&gt;Here's how you can use CodeStream to shift left with observability:&lt;/p&gt;

&lt;h3&gt;
  
  
  Collaborative observability
&lt;/h3&gt;

&lt;p&gt;Collaboration is at the heart of this shift-left approach. With CodeStream, I no longer feel like I'm navigating the code wilderness alone. I can discuss, share insights, and even create tasks within my development environment. It's like having a team of fellow adventurers by your side, ready to help you conquer any challenges.&lt;/p&gt;

&lt;p&gt;CodeStream does that by providing telemetry data at the service level, including all related services, to present a general snapshot and overview of what I’m currently working on.&lt;br&gt;
The ability to drill down and get to the golden signals at the code level provides me with more information to understand and improve my code. This integration of observability into my daily practice right inside my favorite IDE makes me so much more efficient.&lt;/p&gt;

&lt;p&gt;I even have all the high level information at hand for my application in order to make sure that the golden signals for my application look healthy. Being able to get such insights into the IDE allows me to stay focused and not have to switch context from my deep development tasks.&lt;/p&gt;

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

&lt;p&gt;In the example below, I’m discussing with another user an idea on how to improve observability insights into the game. Again, I’m not required to switch to a different tool, make screenshots, or copy and paste code-snippets. I have everything right where it belongs from the perspective of a developer.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Early issue detection
&lt;/h3&gt;

&lt;p&gt;CodeStream surfaces performance metrics next to code to speed up error resolution and streamline planning and implementing performance improvements while simplifying code collaboration.&lt;/p&gt;

&lt;p&gt;It’s a huge benefit, because I'm not waiting for issues to show up in production; I'm being alerted as soon as there's a deviation from the expected. It's like having a scout who lets you know when you're veering off course.&lt;/p&gt;

&lt;p&gt;In my case—as you can see in the below screenshot—everything looks good and I can happily continue playing my game.&lt;/p&gt;

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

&lt;p&gt;However, in another example (this time from a different application), things have taken a turn for the worse and the road ahead looks perilous. I see some extreme increase in both error rate as well as duration of different transactions within my application. This is an important indicator for me to swarm with my team and run some troubleshooting sessions in order to get this back to normal.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Root cause analysis
&lt;/h3&gt;

&lt;p&gt;Let’s face it, bugs happen. When the inevitable issue arises, CodeStream has my back with its powerful tools for root cause analysis. I can view information about issues along with the entire stack trace in one unified interface. It's like having a treasure map that guides you to the heart of the issue, saving valuable time and resources. It helps us navigate the complex landscapes of issues efficiently, ensuring that observability is not a distant concept but a part of our entire development journey.&lt;/p&gt;

&lt;p&gt;New Relic CodeStream provides an Errors section that easily allows me to look at all the issues assigned to me so that I can keep track of my to-do list. Additionally, I can also look at other more recent errors that occurred.&lt;/p&gt;

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

&lt;p&gt;From there I can dive deeper into that ZeroDivisionError seen in the previous screenshot. As you can see in the next screenshot, CodeStream provides all relevant information that I need to get to the root cause of the issue. I can click into the first item in the stacktrace…&lt;/p&gt;

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

&lt;p&gt;And follow through to the next item…&lt;/p&gt;

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

&lt;p&gt;Until I reach the actual line of code where the exception occurred.&lt;/p&gt;

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

&lt;p&gt;It’s a little like having the cheat codes, right?!&lt;/p&gt;

&lt;h3&gt;
  
  
  Vulnerability insights at your fingertips
&lt;/h3&gt;

&lt;p&gt;But there's more to this adventure than just performance and reliability. With the New Relic comprehensive platform, you have a vigilant guardian at your side. The New Relic platform is designed to uncover vulnerabilities before they become critical threats. It identifies security issues and provides invaluable insights, all seamlessly integrated into your development environment through New Relic CodeStream.&lt;/p&gt;

&lt;p&gt;Imagine having a security wizard working alongside you as you code. The New Relic platform keeps a watchful eye on your codebase, scanning for vulnerabilities and pinpointing security weak points. It's like having a trusted advisor who ensures that your applications are fortified against potential threats.&lt;/p&gt;

&lt;p&gt;This integrated approach allows you to stay informed about the latest security threats without ever leaving your preferred integrated development environment (IDE). New Relic CodeStream provides you with the security insights you need to protect your applications, all within the familiar and convenient interface of your development environment.&lt;/p&gt;

&lt;p&gt;I can see each of the libraries that are affected by known vulnerabilities.&lt;/p&gt;

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

&lt;p&gt;And can even learn more about a single vulnerability right within my IDE.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Seamless integration
&lt;/h3&gt;

&lt;p&gt;One of the things I love about CodeStream is how seamlessly it fits into my existing workflow. It's like adding a new piece to your armor; it doesn't weigh you down, but it provides an extra layer of protection. I can access New Relic data and insights without leaving my development environment, making observability an integral part of my daily routine.&lt;/p&gt;

&lt;p&gt;Additionally, I can share data and more information about other tools I use on a daily basis, and, more importantly, that are also used by other team members in my organization.&lt;br&gt;
For example, I can easily create comments into messaging tools such as Slack or Microsoft Teams. Or create an issue and share it via any of my favorite tools.&lt;/p&gt;

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

&lt;p&gt;For example, via a GitHub issue.&lt;/p&gt;

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

&lt;p&gt;Which I can then review and track as part of my natural workflow in GitHub itself.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The ongoing quest
&lt;/h3&gt;

&lt;p&gt;Shifting left with observability isn't a one-time adventure; it's a continuous quest for improvement. With the New Relic platform and CodeStream, you can not only track your application's performance over time but also bolster its security defenses. It's data-driven decision-making at its best, helping you evolve and adapt your applications to meet the ever-changing user demands while staying one step ahead of potential vulnerabilities.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In the world of software development, where every line of code can make or break an application, the New Relic platform and CodeStream are the beacons that guide us. They're not just tools; they're trusted companions on our journey to create robust, high-performance, and secure applications. So, heed the call, embark on the adventure, and use the New Relic platform and CodeStream to revolutionize your observability and fortify your application's defenses. Your users will be the ones to tell the tales of your legendary, reliable, and secure applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;As you embark on your journey to shift left with observability and embrace the power of New Relic CodeStream, why not experience it firsthand? New Relic offers a free account to get you started on your path to creating high-performance, reliable, and secure applications. Take the first step, sign up for your free account, and witness the transformation of your development process.&lt;/p&gt;

&lt;p&gt;Your adventure in the world of observability, performance optimization, and security begins here. With New Relic CodeStream as your trusty companion, you're ready to conquer any challenge that comes your way. &lt;a href="https://newrelic.com/signup?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q3-devtoupdates"&gt;Sign up today&lt;/a&gt;, and let's make your applications legendary.&lt;/p&gt;

</description>
      <category>observability</category>
      <category>devops</category>
      <category>productivity</category>
      <category>development</category>
    </item>
    <item>
      <title>How to monitor Microsoft 365: Getting started</title>
      <dc:creator>Harry Kimpel</dc:creator>
      <pubDate>Tue, 17 Oct 2023 18:34:35 +0000</pubDate>
      <link>https://forem.com/newrelic/how-to-monitor-microsoft-365-getting-started-38pd</link>
      <guid>https://forem.com/newrelic/how-to-monitor-microsoft-365-getting-started-38pd</guid>
      <description>&lt;p&gt;&lt;em&gt;To read this full article, &lt;a href="https://newrelic.com/blog/how-to-relic/how-to-monitor-microsoft-365-getting-started?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q3-How-to-monitor-Microsoft-365"&gt;click here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;One of the most popular and widely adopted business tool suites is Microsoft 365 (previously known as Microsoft Office 365) or just M365 for short. M365 offers a comprehensive array of applications and services, including email, document collaboration, video conferencing, and more, all hosted on the cloud. M365 provides numerous benefits in terms of flexibility and accessibility, but it also introduces new security, performance, and compliance challenges. This is why monitoring M365 applications is critically important for organizations of all sizes. Monitoring M365 applications is essential in helping businesses maintain a secure, efficient, and compliant digital environment.&lt;/p&gt;

&lt;p&gt;In this blog, we’ll look into various parts of the M365 ecosystem. We begin our monitoring journey by gathering information about the health status of the services used within your subscription. We’ll then dig a little deeper into real-time user login monitoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Health information of your services subscribed by a tenant
&lt;/h2&gt;

&lt;p&gt;In M365 adding agents for monitoring isn't possible. Microsoft offers M365 as a software as a service (SaaS); hence, you don’t have access to the underlying infrastructure and cannot install any language or infrastructure agents. To collect health metrics, we have to rely on native Microsoft tools and APIs by utilizing features like the Health Dashboard, auditing, reporting, and the Microsoft Graph API to track and analyze M365 performance, security, and user activity. This ensures a reliable digital workplace without third-party monitoring agents.&lt;/p&gt;

&lt;p&gt;As part of &lt;a href="https://learn.microsoft.com/en-us/graph/"&gt;Microsoft Graph REST APIs&lt;/a&gt;, we’re able to gather information about the &lt;a href="https://learn.microsoft.com/en-us/graph/api/resources/servicehealth?view=graph-rest-1.0"&gt;health information&lt;/a&gt; of services subscribed by a tenant.&lt;/p&gt;

&lt;p&gt;An easy way to gather this information on a continuous basis is to run a &lt;a href="https://docs.newrelic.com/docs/synthetics/synthetic-monitoring/scripting-monitors/write-synthetic-api-tests/"&gt;New Relic synthetic scripted API&lt;/a&gt; monitor.&lt;/p&gt;

&lt;p&gt;Follow these steps to get started:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new &lt;a href="https://newrelic.com/platform/synthetics"&gt;synthetic monitor&lt;/a&gt; and then select Endpoint availability (Scripted API) as the monitor type.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;Select your account and provide a name of the monitor; for example, Microsoft 365 - service overview. Period should be set to the interval on how often you want to get the data for the service health.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Click Select locations and select a single location (in our case it doesn’t make sense to provide additional information when running the API call from many locations).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Continue to the Write script section. Here we’ll write the API test for the service health overview. Copy and paste the below script content into the Script editor.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click Save monitor.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Feel free to explore, or check out the full documentation
 * https://docs.newrelic.com/docs/synthetics/new-relic-synthetics/scripting-monitors/writing-api-tests
 * for details.
 */&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;assert&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;assert&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;request&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="s2"&gt;request&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;promisify&lt;/span&gt; &lt;span class="p"&gt;}&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="s2"&gt;util&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;promisifyRequestGet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;promisify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&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;promisifyRequestPost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;promisify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * VARIABLE DEFINITIONS
 */&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;MSFT_GRAPH_ACCESS_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$secure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MSFT_GRAPH_ACCESS_TOKEN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;NEW_RELIC_ACCOUNT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$secure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEW_RELIC_ACCOUNT_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;NEW_RELIC_INSIGHTS_INSERT_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$secure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEW_RELIC_INSIGHTS_INSERT_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;MSFT_TENANT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$secure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MSFT_TENANT_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;MSFT_CLIENT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$secure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MSFT_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;MSFT_CLIENT_SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$secure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MSFT_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;MSFT_USERNAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$secure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MSFT_USERNAME&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;MSFT_USER_PASSWORD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$secure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MSFT_USER_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;NEW_RELIC_EVENT_TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;M365ServiceOverview&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Function to post events to the New Relic Events API
 * @param {*} body 
 * @returns {Promise&amp;lt;request.Response&amp;gt;}
 */&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;insertInsightsEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;promisifyRequestPost&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://insights-collector.newrelic.com/v1/accounts/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;NEW_RELIC_ACCOUNT_ID&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/events&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&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;X-Insert-Key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NEW_RELIC_INSIGHTS_INSERT_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&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;application/json&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="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Function to record data in NRDB.
 * @param events
 * @returns {Promise&amp;lt;void&amp;gt;}
 */&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;recordData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//console.log(events);&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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;insightsResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;insertInsightsEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&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;insightsResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&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;insertInsightsEvent() non-200 return code: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;insightsResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&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;Script executed successfully&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="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;urlAuthBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;urlAuthBody&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client_id=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;MSFT_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;urlAuthBody&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;client_secret=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;MSFT_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;urlAuthBody&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;response_type=token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;urlAuthBody&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;scope=ServiceHealth.Read.All%20user.read%20openid%20profile%20offline_access&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;urlAuthBody&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;username=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;MSFT_USERNAME&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;urlAuthBody&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;password=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;MSFT_USER_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;urlAuthBody&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;grant_type=password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;urlAuthBody&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;Content-Type&lt;/span&gt;&lt;span class="dl"&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;application/x-www-form-urlencoded&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="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;urlAuth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://login.microsoftonline.com/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;MSFT_TENANT_ID&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/oauth2/v2.0/token?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;$http&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="nx"&gt;urlAuth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// Callback&lt;/span&gt;
    &lt;span class="nf"&gt;function &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="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Expected a 200 OK response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;responseStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;jsonResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseStr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&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;Authorization&lt;/span&gt;&lt;span class="dl"&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;Bearer &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;jsonResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Host&lt;/span&gt;&lt;span class="dl"&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;graph.microsoft.com&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;$http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// Callback&lt;/span&gt;
            &lt;span class="nf"&gt;function &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="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Expected a 200 OK response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;responseStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;jsonResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseStr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="c1"&gt;//console.log('Response: ' + JSON.stringify(jsonResponse.value));   &lt;/span&gt;
                &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;jsonResponseValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="nx"&gt;jsonResponseValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;NEW_RELIC_EVENT_TYPE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LOCATION&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;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;serviceDegradation&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;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusVal&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="k"&gt;else&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;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;serviceOperational&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;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusVal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="nf"&gt;recordData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonResponseValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also find the &lt;a href="https://github.com/harrykimpel/newrelic-microsoft-observability/blob/main/service-health-overview/service-health-overview.js"&gt;latest version of the script&lt;/a&gt; in my &lt;a href="https://github.com/harrykimpel/newrelic-microsoft-observability"&gt;GitHub repository&lt;/a&gt; that accompanies this blog series.&lt;/p&gt;

&lt;p&gt;As you can see in the code, it leverages some secure credentials that are being stored in New Relic. Create each of these &lt;a href="https://one.newrelic.com/synthetics/secure-credential-list"&gt;secure credentials&lt;/a&gt; in your New Relic synthetics environment. The credentials are as follows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Secure Credential&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NEW_RELIC_ACCOUNT_ID&lt;/td&gt;
&lt;td&gt;Your New Relic account ID used to store the custom events from the synthetic check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NEW_RELIC_INSIGHTS_INSERT_KEY&lt;/td&gt;
&lt;td&gt;Your Insights insert key for your New Relic account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSFT_TENANT_ID&lt;/td&gt;
&lt;td&gt;Microsoft 365 tenant ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSFT_CLIENT_ID&lt;/td&gt;
&lt;td&gt;Azure AD app registration client ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSFT_CLIENT_SECRET&lt;/td&gt;
&lt;td&gt;Azure AD app registration client secret&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSFT_USERNAME&lt;/td&gt;
&lt;td&gt;Azure AD username of the user on whose behalf this check is executed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSFT_USER_PASSWORD&lt;/td&gt;
&lt;td&gt;Azure AD password on whose behalf this check is executed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The logic of the script can roughly be broken down in three pieces:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Retrieving an access token for the user and the “virtual application” by making a request to &lt;strong&gt;&lt;a href="https://login.microsoftonline.com/%7Btenant%7D/oauth2/v2.0/token"&gt;https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token&lt;/a&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Leveraging the retrieved token from step 1 and making a call to Microsoft Graph API (that is, &lt;strong&gt;&lt;a href="https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews"&gt;https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews&lt;/a&gt;&lt;/strong&gt;) to retrieve the service health information.&lt;/li&gt;
&lt;li&gt;Sending the gathered information as a custom event to the New Relic platform by calling &lt;strong&gt;recordData(events)&lt;/strong&gt;, which in turn then posts the data with &lt;strong&gt;insertInsightsEvent(body)&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now that we have the health data for all of our M365 services within our subscription reported in New Relic, it’s time for us to visualize the data. The following screenshot shows a sample representation of a dashboard that can be used for getting an up-to-date overview of the service health information. The JSON representation of the dashboard that you can use to import into your New Relic account is available in my GitHub repository &lt;a href="https://github.com/harrykimpel/newrelic-microsoft-observability/blob/main/service-health-overview/service-health-overview-dashboard.json"&gt;here&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;As you can see, we can easily identify the number of services reporting some kind of degradation in the pie chart on the top left. The right-hand side shows more detail about which services are impacted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The dashboard above uses a custom visualization called &lt;strong&gt;Status Table Widget&lt;/strong&gt; that’s part of the &lt;a href="https://github.com/newrelic/nr1-status-widgets"&gt;Status Widget Pack&lt;/a&gt;. This custom visualization is provided by New Relic and available in the &lt;strong&gt;Apps&lt;/strong&gt; section under &lt;strong&gt;Custom Visualization&lt;/strong&gt; in your New Relic account.&lt;/p&gt;

&lt;p&gt;The event type for the above-mentioned data is stored in the event type that can be configured in the API script by defining the &lt;strong&gt;NEW_RELIC_EVENT_TYPE&lt;/strong&gt; variable. All the raw data is available as usual in the &lt;strong&gt;Query Your Data&lt;/strong&gt; part of the New Relic platform.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  User login monitoring
&lt;/h2&gt;

&lt;p&gt;A typical request from customers that I often hear is: “I want to be able to understand how the user experience looks for my internal users.” The information that I’m typically interested in includes the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Availability of the M365 websites (for example, login.microsoftonline.com).&lt;/li&gt;
&lt;li&gt;Latency of the connection to M365 websites.&lt;/li&gt;
&lt;li&gt;A report that shows the latency and the availability over time (for example, the last 30 days).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A pretty straightforward way to achieve this is by leveraging a &lt;a href="https://docs.newrelic.com/docs/synthetics/synthetic-monitoring/scripting-monitors/introduction-scripted-browser-monitors/"&gt;New Relic synthetic scripted browser&lt;/a&gt; monitor.&lt;/p&gt;

&lt;p&gt;Use the steps below to create and configure the synthetic scripted browser:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new synthetic monitor and select &lt;strong&gt;User flow / functionality (Scripted Browser)&lt;/strong&gt; as the monitor type.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;Select your account and provide a name of the monitor; for example, &lt;strong&gt;Microsoft 365 - user login&lt;/strong&gt;. Period should be set to the interval on how often you want to run the checks for the user login.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Click Select locations and select all the locations that are relevant for your business. These locations should be close to your end users who are typically using M365 on a daily business basis.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Continue to the Write script section. Here we’ll write the scripted test for the user login workflow. Copy and paste the below script content into the Script editor.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click Save monitor.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Feel free to explore, or check out the full documentation
 * https://docs.newrelic.com/docs/synthetics/new-relic-synthetics/scripting-monitors/writing-scripted-browsers
 * for details.
 */&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;assert&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;assert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;DefaultTimeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// the next function is removed for better reading purposes of the code&lt;/span&gt;
&lt;span class="c1"&gt;// full code available at https://github.com/harrykimpel/newrelic-microsoft-observability/blob/main/synthetics/user-login/user-login.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;

&lt;span class="nx"&gt;$webDriver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCapabilities&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="nf"&gt;function &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;// Test Case: M365  &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="nf"&gt;function &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="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&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="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="nf"&gt;function &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;logger&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Open URL https://portal.office.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M365&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;$webDriver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://portal.office.com&lt;/span&gt;&lt;span class="dl"&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;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e&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="nf"&gt;function &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;logger&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Type username&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M365&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForAndFindElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$selenium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;i0116&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;DefaultTimeout&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;e&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendKeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$secure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MSFT_USERNAME&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="mi"&gt;0&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="nf"&gt;function &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;logger&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Click By.id(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;idSIButton9&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M365&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForAndFindElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$selenium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idSIButton9&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;DefaultTimeout&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;e&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="mi"&gt;0&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="nf"&gt;function &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;logger&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="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sleep a little&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M365&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&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="nf"&gt;function &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;logger&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="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Type password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M365&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForAndFindElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$selenium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;i0118&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;DefaultTimeout&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;e&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendKeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$secure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MSFT_USER_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="mi"&gt;0&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="nf"&gt;function &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;logger&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="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Click By.id(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;idSIButton9&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M365&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForAndFindElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$selenium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idSIButton9&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;DefaultTimeout&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;e&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="mi"&gt;0&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="nf"&gt;function &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;logger&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="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Click By.css(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;span&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M365&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForAndFindElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$selenium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;span&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;DefaultTimeout&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;e&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="mi"&gt;0&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="nf"&gt;function &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;logger&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="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Click By.id(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;idBtn_Back&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M365&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForAndFindElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$selenium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idBtn_Back&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;DefaultTimeout&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;e&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="mi"&gt;0&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="nf"&gt;function &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;logger&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="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Click By.id(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;officeHome__content&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M365&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForAndFindElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$selenium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;officeHome__content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;DefaultTimeout&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;e&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="mi"&gt;0&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="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M365&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="nf"&gt;function &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="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M365&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;throw &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="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;You can also find the &lt;a href="https://github.com/harrykimpel/newrelic-microsoft-observability/blob/main/user-login/user-login.js"&gt;latest version of the script&lt;/a&gt; in my &lt;a href="https://github.com/harrykimpel/newrelic-microsoft-observability"&gt;GitHub repository&lt;/a&gt; that accompanies this blog series.&lt;/p&gt;

&lt;p&gt;As you can see in the code, it leverages some secure credentials that are being stored in New Relic. Go ahead and create each of these &lt;a href="https://one.newrelic.com/synthetics/secure-credential-list"&gt;secure credentials&lt;/a&gt; in your New Relic synthetics environment. The credentials are as follows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Secure Credential&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MSFT_USERNAME&lt;/td&gt;
&lt;td&gt;Azure AD username of the user on whose behalf this check is executed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSFT_USER_PASSWORD&lt;/td&gt;
&lt;td&gt;Azure AD password on whose behalf this check is executed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The logic of the script can roughly be broken down into three pieces:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Access &lt;strong&gt;&lt;a href="https://portal.office.com"&gt;https://portal.office.com&lt;/a&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Enter username and password for a test user from secure credentials.&lt;/li&gt;
&lt;li&gt;Log in the user and enter the M365 portal home page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The script will now continuously execute based on the specified period for the locations selected. If all of the steps are successful, then the status of the overall script execution will be successful. In case of any errors or timeouts, the script will report a script failure. All of the data is available in the &lt;a href="https://docs.newrelic.com/docs/synthetics/synthetic-monitoring/pages/synthetic-monitoring-summary/"&gt;New Relic synthetics UI&lt;/a&gt;.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;The two aspects mentioned above provide an easy way to get started on your journey to observe your Microsoft 365 environment. New Relic provides a free full-access account that you can use to set up service health monitoring as well as the user login monitoring scripts. Get started today!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Not an existing New Relic user? &lt;a href="https://newrelic.com/signup?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=global-fy24-q3-devtoupdates"&gt;Sign up for a free account&lt;/a&gt; to get started!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>observability</category>
      <category>microsoft365</category>
      <category>tutorial</category>
      <category>newrelic</category>
    </item>
  </channel>
</rss>
