Trace the location of API requests


These days, it’s hard to imagine an app that’s not using APIs. APIs give us access to data and insights and allow us to integrate with cloud services easily. But as we use more and more APIs, we struggle with debugging our app. What if a request fails? What if we need to change something about a particular request? How do we know where in our app the request is coming from?

Tracing API requests

If you call just a handful of API endpoints, and they’re distinct to the specific portions of your app, it’s easy to find where the request is coming from. But as your app grows, and you have more and more API requests, it becomes harder to trace the location of each request. It becomes even harder when you use SDKs or libraries and can’t search for a specific API URL in your code. What if you could automatically trace the location of each API request in your app? What if each request contained information about where in your code it was called from?

Tracing API requests in .NET

To trace the location of API requests in .NET, you need a custom delegating handler. This handler automatically adds information about the location in your code where each API request has been called.

using System.Diagnostics;

public class CodeLocationDelegatingHandler(HttpMessageHandler innerHandler) : DelegatingHandler(innerHandler)
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // using Ben.Demystifier to get clearer information
        var enhTrace = EnhancedStackTrace.Current();
        // skip 3 frames which include this delegating handler and preparing
        // the request
        var frame = (EnhancedStackFrame)enhTrace.GetFrame(3);
        var method = frame.MethodInfo.Name;
        // or using System.Diagnostics.StackTrace, but it's less clear
        // var frame = new StackFrame(0, true);
        // var method = frame.GetMethod()?.Name ?? string.Empty;
        var fileName = frame.GetFileName();
        var lineNumber = frame.GetFileLineNumber();

        request.Headers.Add("x-src-method", method);
        request.Headers.Add("x-src", $"{fileName}:{lineNumber}");

        return base.SendAsync(request, cancellationToken);
    }
}

Then, you can use this handler in your HttpClient:

var handler = new CodeLocationDelegatingHandler(new HttpClientHandler());
using var httpClient = new HttpClient(handler);

async Task Method1()
{
    var response = await httpClient.GetStringAsync("https://jsonplaceholder.typicode.com/posts");
    Console.WriteLine(response);
}

await Method1();

// The API request contains the following headers:
// x-src-method: <<Main>$>g__Method1|0
// x-src: C:\MyApp\Program.cs:6

If you looked closely at the code, you might have noticed that we’re using the Ben.Demystifier package. This package helps us get clearer information about the stack trace. We need it because when you use async methods, .NET generates a lot of compiler-generated code, which makes it hard to understand where the request was called from. Also, keep in mind, that the ability to map the location of the request to the specific line in your code depends on the debug symbols (PDB) being available on runtime.

Tracing API requests in JavaScript

In JavaScript, to get the information about the location of the API request, you need to use the stack trace. You can get the stack trace by creating an Error object and reading its stack property. Once you have the location in your code, add it to the request headers. How you do it depends on the library you use to make API requests. For example, if you’re using fetch, you can add the location information to the request headers:

function getCallerLocation(error) {
  const stack = error.stack.split('\n');
  // skip the error message and the current function
  const caller = stack[2].trim();
  const regex = /at (\w+) \(([^)]+)\)/;
  const match = regex.exec(caller);
  const functionName = match[1];
  const filePathLocation = match[2];
  return { functionName, filePathLocation };
}

async function codeLocationFetch(url, options = {}) {
  const { functionName, filePathLocation } = getCallerLocation(new Error());

  const defaultHeaders = {
    'x-src-method': functionName,
    'x-src': filePathLocation
  };

  const mergedOptions = {
    ...options,
    headers: {
      ...defaultHeaders,
      ...options.headers,
    },
  };

  return fetch(url, mergedOptions);
}

async function func1() {
  const res = await codeLocationFetch('https://jsonplaceholder.typicode.com/posts');
  const json = await res.json();
  console.log(json);
}

await func1();

// The API request contains the following headers:
// x-src-method: func1
// x-src: file:///path/to/myapp/index.js:38:15

Privacy

If you don’t want to send the information about your code base to third parties, you could instrument your logging infrastructure to log the information about the source of the request, but then remove it from the request before sending it to the API. This way, you can still trace the location of the request in your logs, but you don’t have to send the information to the API.

Summary

Including the information about where in your code each API request is called helps you trace the location of the request. This is invaluable when you’re debugging your app and need to locate the specific API request in your codebase. By automatically adding the information about the location of the request to requests, you minimize the impact on your development process and ensure that the information is consistently applied across the whole app.

Others found also helpful: