<?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: Karina Babcock</title>
    <description>The latest articles on Forem by Karina Babcock (@karina_babcock).</description>
    <link>https://forem.com/karina_babcock</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%2F3827743%2F65313751-2f40-4c29-bec4-0bf592acf9e3.png</url>
      <title>Forem: Karina Babcock</title>
      <link>https://forem.com/karina_babcock</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/karina_babcock"/>
    <language>en</language>
    <item>
      <title>Let your agents query reality</title>
      <dc:creator>Karina Babcock</dc:creator>
      <pubDate>Mon, 27 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://forem.com/bitdrift/let-your-agents-query-reality-4jo6</link>
      <guid>https://forem.com/bitdrift/let-your-agents-query-reality-4jo6</guid>
      <description>&lt;p&gt;Last week, we released our first step in making observability programmable and agent-ready with the &lt;a href="https://blog.bitdrift.io/post/public-api?utm_campaign=uc-ai&amp;amp;utm_medium=social&amp;amp;utm_source=devto" rel="noopener noreferrer"&gt;bitdrift Public API&lt;/a&gt;. Today, we're announcing the release of &lt;a href="https://github.com/bitdriftlabs/bd-skills/" rel="noopener noreferrer"&gt;bd skills&lt;/a&gt;, a set of skills that give your AI agents access to everything that happens on any of your customers' devices in real time. Together, bd skills and the bitdrift Public API empower the next generation of our mobile observability platform: &lt;a href="https://bitdrift.ai?utm_campaign=uc-ai&amp;amp;utm_medium=social&amp;amp;utm_source=devto" rel="noopener noreferrer"&gt;bitdrift.ai&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agentic AI has fundamentally changed software engineering
&lt;/h2&gt;

&lt;p&gt;Observability no longer has to be a reactive exercise. Rather than drowning in a sea of alerts, charts, and logs, agents can autonomously triage, investigate, debug, and even fix issues as they happen. This allows teams to focus on what really matters: your customers and their experiences.&lt;/p&gt;

&lt;p&gt;Now, those agents, regardless of the model and harness, must have access to high fidelity data to do their job. More importantly they need to be able to iterate in tight feedback loops. In the past that’s been impossible on mobile devices with slow app release cycles and challenging SDK syncs limiting the telemetry being collected. As a result, all other observability tools rely on sampled or stale data. This means you are always working with approximations, partial visibility, historical snapshots, false metrics, and educated guesses.&lt;/p&gt;

&lt;h2&gt;
  
  
  bitdrift: purpose-built for mobile observability
&lt;/h2&gt;

&lt;p&gt;The bitdrift platform uniquely provides a level and quality of data on deployed mobile applications that no one else can. Data is captured on the device, aggregated in real time, and unsampled by default. When you investigate an issue, you are not querying a static dataset. You are interacting with live user behavior from the device.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full-fidelity observability, now accessible to AI agents
&lt;/h2&gt;

&lt;p&gt;Now, our real-time observability system allows you or your agents to dynamically create and collect telemetry on any device instantly. User journeys, performance metrics, and behavioral changes are all available when your agent needs them, not ten days later waiting for an app release. And with our ability to narrowly target exact cohorts and data sets, you won’t blow your context window on useless noise.&lt;/p&gt;

&lt;p&gt;With this release, agents will be able to leverage our powerful CLI and public API to programmatically access and create the core components of the bitdrift platform: workflows, charts, issues, and captured sessions, letting them iteratively query and analyze telemetry from customer devices.&lt;/p&gt;

&lt;p&gt;You can create and manage workflows in code, retrieve full-fidelity sessions on demand, and integrate bitdrift into your existing tooling, your deploy pipelines, or your on-call workflows, all without ever opening a dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let your AI agents query reality today
&lt;/h2&gt;

&lt;p&gt;Get started:&lt;/p&gt;

&lt;p&gt;Learn more about mobile observability for AI agents at &lt;a href="https://bitdrift.ai?utm_campaign=uc-ai&amp;amp;utm_medium=social&amp;amp;utm_source=devto" rel="noopener noreferrer"&gt;bitdrift.ai&lt;/a&gt;, or try it out by running the following in any terminal:&lt;/p&gt;

&lt;p&gt;npx skills add bitdriftlabs/bd-skills&lt;/p&gt;

&lt;p&gt;Full instructions at &lt;a href="https://github.com/bitdriftlabs/bd-skills" rel="noopener noreferrer"&gt;https://github.com/bitdriftlabs/bd-skills&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not quite ready to dive in but curious to learn more about bitdrift? Sign up for a &lt;a href="https://bitdrift.io/signup?utm_campaign=uc-ai&amp;amp;utm_medium=social&amp;amp;utm_source=devto" rel="noopener noreferrer"&gt;free trial&lt;/a&gt; of bitdrift's mobile observability platform, or contact us for a &lt;a href="https://bitdrift.io/contact-us?utm_campaign=uc-ai&amp;amp;utm_medium=social&amp;amp;utm_source=devto" rel="noopener noreferrer"&gt;quick demo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>observability</category>
      <category>mobile</category>
      <category>feature</category>
      <category>ai</category>
    </item>
    <item>
      <title>Announcing OpenTelemetry mobile tracing (that actually works)</title>
      <dc:creator>Karina Babcock</dc:creator>
      <pubDate>Fri, 24 Apr 2026 13:54:21 +0000</pubDate>
      <link>https://forem.com/bitdrift/announcing-opentelemetry-mobile-tracing-that-actually-works-5fo0</link>
      <guid>https://forem.com/bitdrift/announcing-opentelemetry-mobile-tracing-that-actually-works-5fo0</guid>
      <description>&lt;h1&gt;
  
  
  Why is mobile tracing very hard?
&lt;/h1&gt;

&lt;p&gt;Let’s talk about why tracing from the mobile client is very difficult, especially at large scale. In any moderately large deployment, backend traces are sampled to control costs. Less sophisticated deployments use random head sampling, deciding at the beginning of the trace whether it is sampled or not). More sophisticated deployments use tail sampling, like the &lt;a href="https://opentelemetry.io/docs/concepts/sampling/" rel="noopener noreferrer"&gt;OpenTelemetry Tail Sampling Processor&lt;/a&gt;. In these deployments, a trace data is held for a period of time while waiting for a signal to flush the entire trace or not (for example if one of the operations results in an error the entire trace can be flushed).&lt;/p&gt;

&lt;p&gt;In a best case scenario with tail sampling, the tail sampling buffer is generally tuned to hold trace data for a very short period of time (minutes at most) because the primary use cases involve back-end network calls and their associated microservices. Compare this to the length of mobile sessions which could be hours or even days!&lt;/p&gt;

&lt;p&gt;So fundamentally an engineer who wants to see a mobile session with full backend tracing has to make sure that all network calls in that session are force sampled. But how can this be done sanely? The naive approach is to simply head sample from the client on a strict % basis. This is what every other solution on the market does, which means you’re effectively rolling the dice on whether you’ll have the data you need when something breaks.&lt;/p&gt;

&lt;p&gt;However, bitdrift’s &lt;a href="https://blog.bitdrift.io/post/introducing-tracing" rel="noopener noreferrer"&gt;dynamic control&lt;/a&gt; allows us to fundamentally change how users think about this. &lt;a href="https://bitdrift.io/" rel="noopener noreferrer"&gt;bitdrift Capture&lt;/a&gt; can provide a tracing experience that is much more likely to capture the customer sessions with traces that engineers actually care about.&lt;/p&gt;

&lt;h1&gt;
  
  
  Mobile tracing in Capture
&lt;/h1&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%2F2525d5o8g6n5vlmlle2x.webp" 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%2F2525d5o8g6n5vlmlle2x.webp" alt="Start tracing workflow action" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Today we are launching a few different things that together provide a novel, holistic mobile tracing experience:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The workflow engine now supports a sampled matcher.&lt;/strong&gt; This allows flows to only progress a certain % of the time. This feature is not specific to tracing, though it is especially useful in that context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The workflow engine now supports a “start tracing” action.&lt;/strong&gt; This allows client side traces to be enabled based on workflow progression.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The mobile &lt;a href="https://bitdrift.io/feature/performance-centric" rel="noopener noreferrer"&gt;SDK&lt;/a&gt; now supports an “Is tracing active?” API.&lt;/strong&gt; The out of the box network integrations have been updated to respect that API and if tracing is enabled, will initiate W3C (OTel) traces on network requests with force sampled set to true against every outbound network call. The created trace ID is then stored in network response log fields. If the session is ultimately flushed and captured, the session timeline UI has gained the ability to deep link to the user’s trace provider of choice to see the full backend trace. For those that are not using our OOTB network integrations, the API can still be used to decide whether trace headers should be added or not.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Together, these capabilities let you deterministically capture full, end-to-end traces for the sessions you care about, without permanently increasing cost or overhead. Now, let’s cover some use case examples to fully understand how this is mobile tracing finally done right.&lt;/p&gt;

&lt;h1&gt;
  
  
  Mobile tracing use cases
&lt;/h1&gt;

&lt;p&gt;Here are a few common aspirational tracing use cases that show how the new mobile tracing support provides actionable insights quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  On-demand tracing of a specific target population
&lt;/h2&gt;

&lt;p&gt;Let’s say that you are rolling out a new feature and want to capture sessions with tracing specific to that feature. Using &lt;a href="https://blog.bitdrift.io/post/new-workflows-ui" rel="noopener noreferrer"&gt;workflows&lt;/a&gt; in bitdrift Capture, you can match on feature exposure, then start tracing. After some period of time (via timeout) or until some action occurs (the feature is used) the session can be flushed.&lt;/p&gt;

&lt;p&gt;Between the time tracing was started until the session stops flushing, all network calls will be traced from the client. This guarantees that captured sessions have full backend traces to view. When enough sessions have been captured, the workflow can be dynamically stopped to avoid further tracing overhead. This gives you high-fidelity traces exactly when you need them, and zero waste when you don’t.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting traces for a specific failure flow across a very large population
&lt;/h2&gt;

&lt;p&gt;In another scenario, let’s say you have a massive mobile app with hundreds of millions of users that sells widgets. Given the massive deployment size, widget checkout is going to fail for one reason or the other a bunch of times per day across the entire population. It would be extremely useful to get full end-to-end traces of a failed checkout experience. In order to do this, you can create a workflow that has the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Match on app open with a sample rate of 0.001%.&lt;/li&gt;
&lt;li&gt;Wait until checkout fails.&lt;/li&gt;
&lt;li&gt;Flush the session.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Probabilistically, every day you are going to capture full sessions with complete end-to-end tracing to help with our understanding of how the backend is involved with checkout failures.&lt;/p&gt;

&lt;h1&gt;
  
  
  Tracing done right
&lt;/h1&gt;

&lt;p&gt;Gone are the days of performing head sampling on the mobile client and hoping to get lucky with matching backend traces. (Reality check: you never get lucky when you actually need it.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;bitdrift Capture’s mobile tracing integration gives you full real-time control over what session gets traced, allowing you to get the traces you need when you need them.&lt;/strong&gt; You’ll move from hypothesis to evidence-based understanding of what’s really happening in a single session.&lt;/p&gt;

&lt;p&gt;This is just the beginning of our tracing roadmap and we plan on adding a bunch of other features in the future, including the ability to show server spans directly in our session trace view. We can’t wait to iterate based on your feedback!&lt;/p&gt;

&lt;h1&gt;
  
  
  Join us for the future of observability
&lt;/h1&gt;

&lt;p&gt;Mobile tracing is available today for all customers. New and existing customers can &lt;a href="https://bitdrift.io/signup" rel="noopener noreferrer"&gt;contact us&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;p&gt;Capture is changing the mobile observability game by adding a control plane and local storage on every mobile device, providing extremely detailed telemetry when you need it, and none when you don’t. If lack of mobile tracing was keeping you away, now is the time to give us a try!&lt;/p&gt;

&lt;p&gt;Interested in learning more? Here are some options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://bitdrift.io/contact-us" rel="noopener noreferrer"&gt;Get in touch&lt;/a&gt; for a demo.&lt;/li&gt;
&lt;li&gt;Check out &lt;a href="https://bitdrift.io/sandbox" rel="noopener noreferrer"&gt;the sandbox&lt;/a&gt; or start a &lt;a href="https://bitdrift.io/signup" rel="noopener noreferrer"&gt;free trial&lt;/a&gt; to dive right into the product and see what working with Capture is like.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can’t wait to see what you think.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is OpenTelemetry mobile tracing?
&lt;/h3&gt;

&lt;p&gt;OpenTelemetry mobile tracing extends distributed tracing to the mobile client, allowing engineers to track requests from a user’s device through backend services. Unlike traditional backend-only tracing, it captures the full end-to-end flow of a user session, including client-side context and network interactions.&lt;/p&gt;

&lt;p&gt;If you’re new to OpenTelemetry, you can start with this overview: &lt;a href="https://blog.bitdrift.io/post/what-is-opentelemetry" rel="noopener noreferrer"&gt;https://blog.bitdrift.io/post/what-is-opentelemetry&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What problems does mobile tracing help solve?
&lt;/h3&gt;

&lt;p&gt;Mobile tracing helps engineers debug issues that are difficult or impossible to reproduce locally, such as intermittent failures, long-tail bugs, and complex backend interactions triggered from the client.&lt;/p&gt;

&lt;p&gt;By providing full end-to-end visibility into real user sessions, it allows teams to move from guesswork to precise root cause analysis much faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is mobile tracing harder than backend tracing?
&lt;/h3&gt;

&lt;p&gt;Mobile tracing is more complex because mobile sessions can last much longer (hours or days), data may arrive late or not at all, and cost constraints make continuous tracing impractical. Traditional sampling approaches used in backend systems (like head or tail sampling) don’t translate well to mobile environments and often miss the exact sessions engineers need to debug.&lt;/p&gt;

&lt;p&gt;For a deeper perspective on the limitations of OpenTelemetry in real-world systems, see: &lt;a href="https://blog.bitdrift.dev/post/reality-check-otel" rel="noopener noreferrer"&gt;https://blog.bitdrift.dev/post/reality-check-otel&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How does bitdrift’s OpenTelemetry tracing work differently?
&lt;/h3&gt;

&lt;p&gt;bitdrift uses dynamic workflows to control when tracing is enabled on the client. Instead of randomly sampling sessions, engineers can deterministically start tracing based on specific conditions, such as feature usage or error states, and ensure all related network calls are fully traced.&lt;/p&gt;

&lt;p&gt;This approach provides complete, high-fidelity traces for the sessions that matter most, without requiring engineers to “get lucky” with sampling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does OpenTelemetry mobile tracing require constant sampling?
&lt;/h3&gt;

&lt;p&gt;No. With bitdrift, tracing is activated only when needed using real-time workflows. This avoids the need for constant head-based sampling and reduces unnecessary overhead.&lt;/p&gt;

&lt;p&gt;Teams can capture full traces for targeted sessions and then stop tracing dynamically, balancing visibility with cost.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>mobile</category>
      <category>monitoring</category>
      <category>performance</category>
    </item>
    <item>
      <title>Device performance signals to log for mobile observability</title>
      <dc:creator>Karina Babcock</dc:creator>
      <pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://forem.com/bitdrift/device-performance-signals-to-log-for-mobile-observability-22kk</link>
      <guid>https://forem.com/bitdrift/device-performance-signals-to-log-for-mobile-observability-22kk</guid>
      <description>&lt;h2&gt;
  
  
  Mobile apps are guests on the device; respect the environment
&lt;/h2&gt;

&lt;p&gt;Unlike servers, mobile apps run on hardware with constantly shifting constraints: limited memory, unpredictable networks, and thermal throttling. Monitoring system signals like memory pressure, network usage, and device temperature helps explain performance issues that traditional crash or latency metrics often miss.&lt;/p&gt;

&lt;p&gt;In addition to logging important user experience signals (which I wrote about in &lt;a href="https://blog.bitdrift.io/post/mobile-logging-ux-signals" rel="noopener noreferrer"&gt;my last post&lt;/a&gt;), mobile observability requires logging device performance signals like memory pressure, network behavior, and thermal state.&lt;/p&gt;

&lt;p&gt;Here are some device-level signals every mobile team should be monitoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory pressure
&lt;/h2&gt;

&lt;p&gt;One of the most common mobile performance killers is memory pressure. Memory leaks on mobile are often misdiagnosed as random crashes. When the OS kills your app because it’s resource hungry, it doesn’t always look like a crash in your logs.&lt;/p&gt;

&lt;p&gt;To detect memory-related issues early, mobile teams should monitor metrics such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;memory footprint over time&lt;/li&gt;
&lt;li&gt;OS low memory warnings&lt;/li&gt;
&lt;li&gt;foreground vs background memory usage&lt;/li&gt;
&lt;li&gt;memory growth across a user session&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If memory usage steadily increases during a session without returning to baseline, it’s often a sign of a leak. Identifying this trend early can prevent OS-level out-of-memory (OOM) terminations that are difficult to diagnose after the fact. Tracking memory pressure events and low memory warnings from the OS allows you to see the “cliff” before your users fall off it.&lt;/p&gt;

&lt;p&gt;This is another pattern we’ve worked hard to surface at bitdrift by capturing deep memory footprint metrics during sessions, along with app lifecycle context. We did a deep dive on how we handle this &lt;a href="https://blog.bitdrift.io/post/memory-leaks" rel="noopener noreferrer"&gt;here&lt;/a&gt;, but the short of it is: &lt;em&gt;We can show you a memory usage graph for every captured session.&lt;/em&gt; bitdrift provides deep memory footprint monitoring and it can even capture if the app is in the foreground or background.&lt;/p&gt;

&lt;h2&gt;
  
  
  Network behavior
&lt;/h2&gt;

&lt;p&gt;Mobile networks are unpredictable. A request that performs perfectly in the office on Wi-Fi may behave very differently on a congested cellular network.&lt;/p&gt;

&lt;p&gt;Many teams track network latency, but a single latency metric rarely tells the full story. To understand how network conditions affect user experience, teams should monitor signals such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request latency distribution (P50, P95, P99)&lt;/li&gt;
&lt;li&gt;network type (Wi-Fi vs cellular)&lt;/li&gt;
&lt;li&gt;request retries and failures&lt;/li&gt;
&lt;li&gt;bandwidth consumption per session&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bandwidth usage is particularly important on mobile. Some apps unknowingly burn through a user’s data plan due to aggressive telemetry, large payloads, or chatty third-party SDKs.&lt;/p&gt;

&lt;p&gt;Another common blind spot is treating a network request as a single latency number. In reality, it includes multiple phases: DNS lookup, TCP/TLS handshake, request transmission, and response time. Without visibility into those steps, it’s difficult to determine whether a slowdown is caused by the backend, the network, or the device itself.&lt;/p&gt;

&lt;p&gt;When we built bitdrift, we wanted to expose this deeper level of network visibility. Most tools tell you if an API call failed; bitdrift tells you how much that API call cost the device by looking at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bytes Per Minute:&lt;/strong&gt; We monitor network consumption with high granularity. This allows you to identify “bandwidth hogs.” In practice, these are often 3rd party SDKs or even other observability tools that are over collecting data and burning through the user’s data plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network Visibility:&lt;/strong&gt; It’s excellent that some teams are tracking client-side network latency. But treating a network request as a single latency metric can lead to, you guessed it, massive blindspots on mobile. We designed bitdrift to break down the socket’s life into its actual components. We do this by tracking the fetch initialization, DNS resolution, TCP/TLS handshakes, and response latency in high resolution. By capturing and plotting these in a waterfall chart, we let you definitively distinguish between an environmental network constraint and a backend performance issue.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Thermal state tracking
&lt;/h2&gt;

&lt;p&gt;Mobile devices dynamically adjust CPU performance based on temperature. When a phone starts running hot, the operating system throttles CPU performance to protect the hardware.&lt;/p&gt;

&lt;p&gt;This means an app that performs perfectly under normal conditions can suddenly feel slow if the device is under thermal pressure. Mobile teams should monitor signals like device thermal state and correlate them with performance regressions to understand whether slowdowns are caused by software or by environmental constraints.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A phone that is running hot will throttle the CPU, making your perfectly optimized code run like a turtle.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That’s why bitdrift logs the thermal state of the device. If you see a cluster of performance issues, you can quickly check if they are correlated with high device temperatures, helping you distinguish between a software bug and environmental hardware constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Device performance signals are a critical piece of the mobile observability puzzle. In &lt;a href="https://blog.bitdrift.io/post/mobile-logging-ux-signals" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt; of this series, we explored the user experience signals that reveal how your app feels to users. In this post, we looked at the device-level constraints – memory pressure, network variability, and thermal throttling — that often explain why performance degrades in the real world.&lt;/p&gt;

&lt;p&gt;In the final post of this series, we’ll examine application behavior and contextual UX signals, including lifecycle transitions, feature flag exposure, and session replay, which provide the missing context needed to reproduce and resolve the hardest mobile bugs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is memory pressure in mobile apps?
&lt;/h3&gt;

&lt;p&gt;Memory pressure occurs when an app consumes too much memory relative to what the device can support. This can lead to slowdowns, background kills, or out-of-memory (OOM) terminations. Monitoring memory usage over time helps identify leaks and prevent crashes that are otherwise difficult to diagnose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do mobile apps sometimes crash without clear error logs?
&lt;/h3&gt;

&lt;p&gt;Not all crashes are logged as traditional exceptions. In many cases, the operating system terminates the app due to resource constraints like memory pressure. These OS-level kills often leave little to no diagnostic information unless teams are proactively tracking device performance signals.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does network variability impact mobile performance?
&lt;/h3&gt;

&lt;p&gt;Mobile networks are inherently unstable and can vary based on location, congestion, and connection type (Wi-Fi vs cellular). This variability affects request latency, retries, and failures. Without logging detailed network behavior (including DNS, connection setup, and response time) it’s difficult to pinpoint the root cause of performance issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is thermal throttling and why does it matter?
&lt;/h3&gt;

&lt;p&gt;Thermal throttling occurs when a device reduces CPU performance to prevent overheating. This can make an app feel slow even if the code is well-optimized. Tracking device thermal state helps teams distinguish between performance issues caused by software and those caused by environmental conditions.&lt;/p&gt;

</description>
      <category>observability</category>
      <category>mobile</category>
    </item>
    <item>
      <title>A sneak peek at new bitdrift Workflows UI</title>
      <dc:creator>Karina Babcock</dc:creator>
      <pubDate>Thu, 26 Mar 2026 12:00:00 +0000</pubDate>
      <link>https://forem.com/bitdrift/a-sneak-peek-at-new-bitdrift-workflows-ui-ifk</link>
      <guid>https://forem.com/bitdrift/a-sneak-peek-at-new-bitdrift-workflows-ui-ifk</guid>
      <description>&lt;p&gt;We're about to ship a ground-up redesign of Capture workflows that makes them fast to build, easy to read, and ready for AI agents to create and manage programmatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  A sneak peek at our new Workflows UI
&lt;/h2&gt;

&lt;p&gt;bitdrift Capture workflows have always been powerful. We're about to ship a ground-up redesign that makes them fast to build, easy to read, and (when we release our public API in the very near future) ready for AI agents to create and manage programmatically. This redesign lays the groundwork for first-class tooling support: a public API, CLI access, and the ability for LLM agents to instrument apps and manage workflows autonomously.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick refresher
&lt;/h2&gt;

&lt;p&gt;Capture is a fundamentally different take on mobile observability. Rather than sending non-useful samples or firehosing every log from every device to a backend that processes and discards most of it, Capture keeps data on-device and only ships it when something interesting happens. The mechanism that defines "interesting" is the workflow: a state machine deployed in real time to your fleet that describes sequences of events to match on and actions to take when they do.&lt;/p&gt;

&lt;p&gt;Workflows are powerful. They can also be humbling. Over time, as customers pushed them to their limits, we heard the same feedback again and again: too many clicks, too many advanced features, too much nuance to learn before being productive. We took that feedback seriously and rebuilt the workflow experience from the ground up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's coming
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A faster, cleaner builder
&lt;/h3&gt;

&lt;p&gt;The new builder is optimized for speed. Graph layout is now calculated automatically, drag and drop is gone, and steps are placed contextually as you build. Keyboard shortcuts cover the most common operations, so power users can move quickly without reaching for the mouse. The result is an experience that feels much more like writing code than assembling a flowchart.&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%2Fvqt3b6a21miu9ir2bbmj.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%2Fvqt3b6a21miu9ir2bbmj.png" alt="new-bitdrift-workflow-builder" width="800" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The new workflow builder: Actions are attached, not floating
&lt;/h3&gt;

&lt;p&gt;In the previous builder, actions (record session, plot chart, etc.) were standalone floating nodes connected to matchers by edges. This made complex workflows hard to read at a glance. Actions are now directly attached to the step that triggers them, so the relationship between a match condition and its outcome is immediately obvious.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps have names. Everything follows.
&lt;/h3&gt;

&lt;p&gt;You can now name your workflow steps, and all titles, series names, and chart metadata are inferred from those names automatically. Less busywork, and your charts finally describe what they're actually measuring.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exit conditions
&lt;/h3&gt;

&lt;p&gt;We're generalizing and replacing the previous "timeout rule" with a more powerful and flexible concept: exit conditions. An exit condition can be a timeout or any matcher, and when it fires, it can trigger any combination of actions (charts, record sessions, etc.) just like a normal match step. This makes it straightforward to express patterns like "if the user abandons the checkout flow at any point, record the session."&lt;/p&gt;

&lt;h3&gt;
  
  
  Reset and entrance behavior, made visible
&lt;/h3&gt;

&lt;p&gt;Reset and entrance behavior — the rules that govern when a workflow restarts or re-enters — are now surfaced clearly in the UI rather than buried in configuration drawers. Workflows can now contain multiple independent flows, each with its own entry point, running in parallel. Resets on one flow are isolated from all others.&lt;/p&gt;

&lt;h3&gt;
  
  
  Smarter conditionals
&lt;/h3&gt;

&lt;p&gt;Match conditions now support both ANDs of ORs and ORs of ANDs. Previously, only one form was supported, which forced awkward workarounds for common cases. Both are now first-class.&lt;/p&gt;

&lt;h3&gt;
  
  
  Charts, simplified and split out
&lt;/h3&gt;

&lt;p&gt;The previous "chart" action covered a lot of ground in a single configuration surface. We've split it into focused types (Counters, Histograms, Rate, Average, Funnel, and Sankey), each with a vastly simpler configuration. The "Advanced charts" section is gone. Chart customization (titles, series names, units) is now done directly on the chart itself rather than in the workflow, which means the same underlying data can be visualized differently depending on where it's shown.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measure duration gets more powerful
&lt;/h3&gt;

&lt;p&gt;The measure duration step now supports restricting actions to a specific duration range. For example, only record a session when the time between two steps exceeds 10 seconds. You can also attach any existing action to a measure duration step, giving you full flexibility over what happens when a timing threshold is crossed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The big reason we rebuilt this
&lt;/h2&gt;

&lt;p&gt;Customer feedback drove a lot of this. We heard clearly that workflows needed to be more intuitive, faster to learn, and less prone to surprising behavior. Every item in the list above traces back to a real conversation with a real user who got stuck.&lt;/p&gt;

&lt;p&gt;But there's a bigger reason.&lt;/p&gt;

&lt;p&gt;We're about to launch a public API and CLI that unlock everything the UI can do (and more). AI agents will be able to instrument apps, create workflows, and debug issues autonomously. For that to work, workflows had to be representable as clean, predictable code rather than a sprawling visual graph with implicit rules and hidden state. The redesign wasn't just about making workflows easier for humans. It was about making them machine-readable.&lt;br&gt;
This is the foundation for what comes next.&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%2Fnmt5qcgh0tps93nzobi8.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%2Fnmt5qcgh0tps93nzobi8.png" alt="New bitdrift Workflows UI walk-thru" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://blog.bitdrift.io/v/16514498037657525939/assets/posts/new-workflows-ui/walkthrough.mp4" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;blog.bitdrift.io&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  New Workflows UI walkthrough
&lt;/h2&gt;

&lt;p&gt;Interested in learning more? &lt;a href="https://bitdrift.io/signup?utm_campaign=eg&amp;amp;utm_medium=social&amp;amp;utm_source=reddit" rel="noopener noreferrer"&gt;Get in touch with us for a demo&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://communityinviter.com/apps/bitdriftpublic/bitdrifters" rel="noopener noreferrer"&gt;Please join us in Slack&lt;/a&gt; as well to ask questions and give feedback!&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>observability</category>
    </item>
    <item>
      <title>User experience signals to log for mobile observability</title>
      <dc:creator>Karina Babcock</dc:creator>
      <pubDate>Tue, 17 Mar 2026 17:02:34 +0000</pubDate>
      <link>https://forem.com/bitdrift/user-experience-signals-to-log-for-mobile-observability-3b2e</link>
      <guid>https://forem.com/bitdrift/user-experience-signals-to-log-for-mobile-observability-3b2e</guid>
      <description>&lt;h2&gt;
  
  
  The green dashboard fallacy
&lt;/h2&gt;

&lt;p&gt;Every mobile engineer has experienced this scenario in some form or another: your backend health is green, your crash free rate is at 99.9%. Yet, App Store reviews are flooding in about the app being "slow" or about a broken experience users encountered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's the problem: Mobile apps fail in ways traditional observability tools can't detect and dashboards can't see.&lt;/strong&gt; Teams log for the disaster (the crash) but they're blind to the friction (the jank). The approach is reactive, fragmented, and heavily sampled.&lt;/p&gt;

&lt;p&gt;To build a high performance mobile app in 2026, you need to log the signals that explain how the app behaves on a real device. Your strategy should shine a light on the mobile-specific blind spots that servers simply don't have.&lt;/p&gt;

&lt;p&gt;In this three-part series, we'll unpack the most important signals mobile teams should be capturing, starting with user experience (UX) signals.&lt;/p&gt;

&lt;h2&gt;
  
  
  User experience (UX) signals
&lt;/h2&gt;

&lt;p&gt;The first blindspot in mobile observability is that users experience apps as interactions, not metrics. In other words: Users don't care about your uptime; they care about how the app feels.&lt;br&gt;
Here are some specific metrics to help you understand user experience.&lt;/p&gt;
&lt;h3&gt;
  
  
  Frame drops and UI jank
&lt;/h3&gt;

&lt;p&gt;Mobile users immediately notice frame drops. Even small stutters can make an app feel unreliable. This is one of the biggest blind spots on Android: micro stutters or freezes.&lt;/p&gt;

&lt;p&gt;Aside from being annoying for end users, Google Play penalizes apps' Play Store rankings if they exceed their threshold and your app might even get a warning label on your Google Play store page. Scary, right? To make it worse, you might know your app feels laggy, or you might see a generic slow rendering percentage in Google Play Console, but how do you actually fix that?&lt;/p&gt;

&lt;p&gt;Most tools only show aggregate rendering metrics. They don't tell you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which screen dropped frames&lt;/li&gt;
&lt;li&gt;what the UI state was&lt;/li&gt;
&lt;li&gt;what the device was doing at the time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The good news is that libraries like Android JankStats do expose this information at the frame level. When you log these events with context (screen name, device state, network state), you can pinpoint exactly why rendering fell below 60fps.&lt;/p&gt;

&lt;p&gt;That's why we've &lt;a href="https://blog.bitdrift.io/post/jank-stats-integration?utm_campaign=eg&amp;amp;utm_medium=social&amp;amp;utm_source=devto" rel="noopener noreferrer"&gt;integrated bitdrift&lt;/a&gt; directly with the Android JankStats library. This allows us to track every single frame drop and, crucially, attach UI State to it. By capturing the specific UI state at the exact millisecond a frame exceeds 16ms, you can identify and fix the issues that actually impact your Google Play Store ranking. It's the difference between guessing where a bottleneck lives and having a frame-by-frame receipt of why your app isn't hitting its 60fps target.&lt;/p&gt;
&lt;h3&gt;
  
  
  "Intent-to-action" workflows
&lt;/h3&gt;

&lt;p&gt;Mobile users don't experience apps as isolated functions. They experience workflows. A user taps "Add to Cart," submits a login form, or starts checkout and expects something to happen immediately. When those workflows feel slow or fail silently, users notice. Yet many observability tools focus on backend latency or crashes, leaving the client-side gap between user intent and visible result largely unmeasured.&lt;/p&gt;

&lt;p&gt;Mobile teams should instrument the start and end of key user actions to track workflow latency, completion rate, and timeouts. Measuring the time between events like "Add to Cart tapped" and "Cart updated" reveals whether real user interactions are getting faster or slower across releases. Capturing outcomes (success, retry, failure) and basic context like device model, network type, and UI state helps engineers understand why a workflow is degrading. These signals often surface issues such as network stalls, UI deadlocks, or backend slowdowns that never show up in crash dashboards but still damage the user experience.&lt;/p&gt;

&lt;p&gt;This is another area we've looked closely at as we built our &lt;a href="https://bitdrift.io/?utm_campaign=eg&amp;amp;utm_medium=social&amp;amp;utm_source=devto" rel="noopener noreferrer"&gt;mobile observability solution&lt;/a&gt;. With bitdrift, measuring the time between any two logs is trivial. You don't need to wrap your code in complex stopwatches or spans. If you have a log when a user taps "Add to Cart" and another when a "Success" message appears, you can create a dynamic span in the bitdrift dashboard using workflows to measure that duration across your entire fleet. The best part is that these dynamic spans don't require a new app store release and you can tweak them to broaden the trigger/end point. You have full control.&lt;/p&gt;

&lt;p&gt;Here's a code sample of how you can add a custom log like "Add to Cart" to your code using bitdrift:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;kotlin&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.os.SystemClock&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.bitdrift.capture.Capture&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.UUID&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onAddToCartClicked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;opId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;startedAtMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;elapsedRealtime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// 1) Start marker: user intent + context&lt;/span&gt;
    &lt;span class="nc"&gt;Capture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"event"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"cart_add_initiated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"op_id"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;opId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"product_id"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"price_usd"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="s"&gt;"ui_state"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"product_detail_view"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"interaction_type"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"tap"&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="s"&gt;"Cart.AddInitiated"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;cartRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addToCart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errorCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errorMessage&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;durationMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;elapsedRealtime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;startedAtMs&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&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="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// 2) Success marker: bitdrift now has a clear 'intent-to-action' delta&lt;/span&gt;
            &lt;span class="nc"&gt;Capture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"event"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"cart_add_success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"op_id"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;opId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"product_id"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"duration_ms"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;durationMs&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="s"&gt;"Cart.AddSuccess"&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="c1"&gt;// 3) Non-fatal failure marker: Captured with error context&lt;/span&gt;
            &lt;span class="nc"&gt;Capture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"event"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"cart_add_failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"op_id"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;opId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"duration_ms"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;durationMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"error_type"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"network_timeout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"error_code"&lt;/span&gt; &lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errorCode&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s"&gt;"unknown"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="s"&gt;"error_message"&lt;/span&gt; &lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errorMessage&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s"&gt;"unknown"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="s"&gt;"was_retry"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"false"&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="s"&gt;"Cart.AddFailed"&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;And here's what the resulting log would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;json&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="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cart.AddFailed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"warning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-03T17:41:06Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fields"&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="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cart_add_failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"op_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"f47ac10b-58cc-4372-a567-0e02b2c3d479"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sku_9921_x"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"price_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"45.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"duration_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1240"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"error_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"network_timeout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"error_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"504"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"error_message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gateway Timeout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"was_retry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"false"&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="nl"&gt;"context"&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="nl"&gt;"device_model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pixel 8 Pro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"os_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Android 14"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"network_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cellular"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"thermal_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nominal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"memory_usage_mb"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"412"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"session_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sess_882194"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"app_lifecycle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"foreground"&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="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The "silent" timeout
&lt;/h3&gt;

&lt;p&gt;Workflows aren't just for successes. You can and should also be tracking timeouts. What happens if a user starts a checkout but the order_confirmed log never fires?&lt;br&gt;
One of the most damaging mobile experiences is the silent timeout:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User taps a button&lt;/li&gt;
&lt;li&gt;A loading spinner appears&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Nothing ever finishes&lt;br&gt;
Because these events don't crash the app, they often go completely undetected.&lt;br&gt;
Mobile teams should define expected completion windows for critical workflows. If a workflow does not complete within that window, it should be treated as a failure event.&lt;br&gt;
This helps identify:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Backend stalls&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lost network requests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UI deadlocks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dropped callbacks&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Bonus:&lt;/strong&gt; this is another area where bitdrift can help. With bitdrift's Timeout Matcher, you can set a workflow to trigger an action, like "record session", if a specific sequence of logs doesn't complete within a set timeframe (e.g., 15 seconds). This will help you catch the "spinning loading wheel of death" use cases that don't technically crash but definitely cause churn. Maybe even more important: it also means you get the full user session allowing you to see exactly what happened to the user during this eternity…errr 15 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Crash rates don't tell you how your app actually feels to users. Signals like frame drops, workflow latency, and silent timeouts reveal the friction that traditional observability tools often miss. Logging these UX signals is the first step toward understanding what's really happening on user devices.&lt;br&gt;
Next in this series, we'll look at device performance signals: the memory, network, and thermal metrics that explain why perfectly good code can suddenly slow down in the real world.&lt;/p&gt;

&lt;p&gt;Interested in learning more? Check out the &lt;a href="https://bitdrift.io/sandbox?utm_campaign=eg&amp;amp;utm_medium=social&amp;amp;utm_source=devto" rel="noopener noreferrer"&gt;sandbox&lt;/a&gt; or start a &lt;a href="https://bitdrift.io/signup?utm_campaign=eg&amp;amp;utm_medium=social&amp;amp;utm_source=devto" rel="noopener noreferrer"&gt;free trial&lt;/a&gt; to see what working with Capture is like.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>ux</category>
      <category>observability</category>
    </item>
  </channel>
</rss>
