Scala
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Scala
-
Section 4: Functional Scala
-
Section 5: Collections in Depth
-
Section 6: Type System
-
Section 7: Concurrency and Ecosystem
-
Section 8: Practical Projects
-
Section 9: Interview Practice
-
Section 10: Data Structures and Algorithms in Scala
-
Section 11: More Practice Exercises
-
Section 12: Advanced Functional Patterns
-
Section 13: More Ecosystem
-
Section 14: Scala Collections Library Deep Dive
-
Section 15: Scala Standard Library Deep Dive
-
Section 16: Akka Ecosystem Deep Dive
-
Section 17: Cats and Cats Effect Deep Dive
-
Section 18: Play Framework Deep Dive
-
Section 19: Apache Spark with Scala Deep Dive
-
Section 20: Scala Build Tools Deep Dive
-
Section 21: Scala 3 Specific Features
-
90: Union and Intersection Types
-
Section 22: Scala Testing Deep Dive
-
Section 23: Functional Domain Modeling
-
Section 24: More Data Structures and Algorithms in Scala
-
Section 25: Scala for Data Engineering
-
Section 26: More Practical Projects
-
Section 27: More Interview and Review
-
Section 28: ZIO Ecosystem Deep Dive
-
Section 29: Scala for Machine Learning
-
Section 30: Scala Microservices Architecture
-
Section 31: Scala Type System Deep Dive
-
Section 32: More Practice and Drills
-
Section 33: Scala Performance Deep Dive
-
Section 34: Scala Ecosystem Tooling
-
Section 35: Scala for Reactive Systems
-
Section 36: More Real-World Case Studies
-
Section 37: Scala for Financial Systems
-
Section 38: Scala GraphQL and gRPC
-
Section 39: More Final Projects
-
Section 40: More Interview and Final Review
-
Section 41: Scala for Streaming Data
-
Section 42: Scala Security Practices
-
Section 43: More Language Deep Dive
-
Section 44: Scala Command-Line Tools
-
Section 45: Scala Documentation and Style
-
Section 46: Scala Dependency Management
-
Section 47: More Practical Backend Patterns
-
Section 48: Scala for Event-Driven Architecture
-
Section 49: More Practice Drills Round 2
-
Section 50: Scala Compiler Deep Dive
-
Section 51: Scala for Web Frontends
-
Section 52: More Data Engineering Practice
-
Section 53: Scala Observability
-
Section 54: More Advanced Practice Projects
-
Section 55: Scala for Legacy Java Integration
-
Section 56: More Testing Practice
-
Section 57: Final Mastery Review
-
Section 58: Scala History and Ecosystem Context
-
Section 59: More Concurrency Patterns
-
Section 60: Scala for Configuration Management
-
Section 61: More Domain Modeling Practice
129: Distributed Tracing in Scala Services
Imagine you're tracking a high-priority package across the country. The package has one unique tracking number, but as it moves, it hits several different checkpoints: the local post office, a regional sorting hub, a cargo plane, and finally, another sorting hub in the destination city. Each of those stops logs a "scan" event. If the package disappears, you don't just see that it's "gone"; you can see exactly which hub held onto it for too long or where it was misrouted. That's exactly what distributed tracing does for your requests.
In a Scala microservices environment, your "package" is an incoming HTTP request. The "tracking number" is the Trace ID, and every individual operation—a database query, a call to another service, or a heavy computation—is a Span. The magic happens when you pass that Trace ID from one service to the next, allowing you to reconstruct the entire journey in a tool like Jaeger or Zipkin.
The Postal Service of Microservices
Let's map that analogy directly to how we actually build this. When a request hits your first Scala service, you generate a Trace ID. Every time that request calls another function or service, you create a child Span.
- The Trace ID: The global identifier for the entire request lifecycle. It's like the tracking number on the box.
- The Span: A timed block of work. "Fetching user profile from Postgres" is a span. "Calling the Payment API" is another span.
- Context Propagation: This is the act of stuffing the Trace ID into the HTTP headers (usually as
traceparent) so the next service knows it's part of the same journey.
The Nightmare of Async Boundaries
Here is where things get tricky in Scala. In a simple Java app, you might use a ThreadLocal to store the current trace context. But we aren't writing simple blocking Java; we're using Futures, ZIO, or Cats Effect. Since these libraries shift execution across different threads, a ThreadLocal will lose your trace context the moment you hit a flatMap. I've spent way too many hours debugging "broken traces" because a context wasn't propagated across a thread boundary.
To fix this, we use OpenTelemetry (OTel). Instead of relying on threads, OTel allows us to explicitly wrap our logic or use "context-aware" wrappers that carry the state along with the functional effect. I highly recommend sticking to the OpenTelemetry SDK rather than trying to roll your own header-passing logic—don't reinvent the wheel here.
Wiring up OpenTelemetry in Scala
You don't want to manually start and stop spans everywhere; that would clutter your business logic with boilerplate. Instead, we usually wrap our service calls. Here is a simplified look at how you'd wrap a call to an external Inventory service using the OTel API:
import io.opentelemetry.api.GlobalOpenTelemetry import io.opentelemetry.api.trace.Span import io.opentelemetry.api.trace.Tracer val tracer: Tracer = GlobalOpenTelemetry.getTracer("inventory-service-client") def checkStock(productId: String): Future[Boolean] = { val span = tracer.spanBuilder("checkStock").startSpan() // We make the span 'current' so that any nested spans // (like the actual HTTP client call) know who their parent is. val scope = span.makeCurrent() try { inventoryClient.getStock(productId).map { result => span.setAttribute("product.id", productId) span.setAttribute("stock.available", result) result }.recover { case ex => span.recordException(ex) throw ex } } finally { // Always close the scope and the span, or you'll leak memory scope.close() span.end() } }Notice the
setAttributecall. This is where tracing becomes powerful. Instead of just knowing thatcheckStockwas slow, you can see that it was specifically slow forproductId = "ultra-heavy-item-123". That's the difference between "the system is slow" and "this specific product is causing a database bottleneck."
There are no comments for now.