How to log part of HTTP response header?

Hi,

I am sending HTTP requests to an API, this API responds with a Header that contains a request ID (used to correlate all events inside the API). I want to log this request id with all the metrics etc in k6. I thought I could add it as a custom tag, however I am unable to access the response header inside the tags.

This is what I’m trying to do:

export default function (data) {
const params = {
headers: {
‘Content-Type’: ‘application/json’,
‘Authorization’: Bearer ${data.access_token},
},
tags : {
‘my_reqId’ : ‘???’
}
};
var res = http.get(webapiurl + ‘WhoAmI’, params);
// get the requestId
var reqId = res.headers[“Req_id”];
exec.vu.tags[‘my_reqId’] = reqId;
}

I’ve tried adding the req id via exec.vu.tags, however this seems to always log the request id from the previous http call (tag will be empty for the first call). Is there any way to accomplish this? For us its very important to not only test the performance of the API but also be able to drill down into specific, slow requests.

Hi @123qwdn89q2e

Welcome to the community forum :wave:

What is happening I believe is expected. You can custom tag requests, checks, thresholds or custom metrics. However, since the value reqId will come in a response header, the tag cannot be applied to default metrics or the http request until the next loop. This is also why when setting it via exec.vu.tags you see the initial request has it undefined and then it’s applied to the previous http calls, no the “current” one which has already been issued.

However, you could apply it to a custom metrics, checks (or thresholds). Since you can tag those after reading the response header for the http request.

import http from 'k6/http';
import { Trend } from 'k6/metrics';
import { check } from 'k6';
import exec from 'k6/execution';

const myTrend = new Trend('my_trend');

export default function () {

    const res = http.get('https://httpbin.test.k6.io/response-headers?request-id='+__VU); // create a response header "request-id" with value VU id for testing purposes

    const myRequestId = res.headers["Request-Id"];

    // Add Request-Id tag to a check
    check(res, { 'status is 200': (r) => r.status === 200 }, { requestId: myRequestId });

    // Add Request-Id tag to a custom metric
    myTrend.add(res.timings.connecting, { requestId: myRequestId });
}

That said, allow me a few days to discuss this internally, since I might be missing something. The team members I want to talk to are out for the holiday season and I’ll get back to this thread once they are back. Thanks for your patience.

Cheers!

Hi,

thank you for the swift reply. I’m aware of being able to add it to custom metrics, however this feels like a half-baked solution as I want to capture the requests that are being sent by k6 OOB and be able to trace them inside the API in case of any deviating performance.

With custom metrics, I’d essentially be re-implementing all the metrics that already exist in the http package to make this work and the issue would be that k6 creates a lot of metrics without the request id which makes them almost useless to me and I would have to delete them after the test run.

Speaking of which, would it be possible to create an overload for the http package? I believe what I want to do should’nt be out of the ordinary:

  1. Send a bulk of requests to my API
  2. Log part of the response header within each request
  3. Ingest all the metrics that k6 generates into a Kusto cluster in azure.
  4. Correlate the k6 metrics with my API traces via the request id from the response headers within Azure Application Insights.

Cheers!

1 Like

Hi Felix,

This is what I understood from your initial question as well. What you are looking for is not re-implementing all the metrics to be able to tag them. It for that you’d need to be able to pass the response header/s that you want to include in the tags. That’s why I’ll discuss this with the team when they are back, as I might have missed an alternative to achieve this. I’ll keep you posted.

In the meantime, I wish you are enjoying the holiday season :christmas_tree:

Cheers!

1 Like

Hi Felix,

Thanks for your patience.

Discussing this with @olegbespalov, he agrees this does not seem possible as is. Metrics are processed independently and at the time you get a response with your Request-Id some of the metrics could have been already pushed to the metrics storage.

A potential alternative Oleg suggested is to use two IDs and log the matching, something like the following:

import { uuidv4 } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js';
import http from 'k6/http';

export default function (data) {
    const httpReqId = uuidv4();
    
    const params = {
        headers: {
            'Content-Type': 'application/json',
            'Authorization': "Bearer ${data.access_token}",
        },
        tags: {
            'my_reqId': httpReqId
        }
    };
    var res = http.get(webapiurl + 'WhoAmI', params);

    // get the requestId
    var reqId = res.headers['Request-Id'];

    console.log("k6 request ID: " + httpReqId, " has been registered with ID: " + reqId);
}

In this way the metrics will have the tag with the UUID generated inside the k6 test, and you still can have a log entry that can help to map the specific k6’s request (and its metrics) with the server logs (and other internal metrics).

If your res.headers['Request-Id']; was somehow permanent, maybe you could even extract the logic of getting it outside of the k6 and later pass it with the k6 run --tag RequestId="$EXTRACTED_ID". If the Request-Id is unique per request (or only some requests), this will not work.

I think we might be able to better advice if we can understand how Request-Id changes in your workflow. Will each user (VU?) get the same Request-Id during the test while invoking different API endpoints (similar to a web session id), or is the Request-Id not sticky? How does the generation of a Request-Id work between different API calls that a single VU does with your script? A better understanding of the context might help us figure out if there is a better approach with k6 at this time.

Hi,

thanks for following up on this! Unfortunately, the suggested approach wouldn’t be ideal as it would mean having to map the (custom ID) against the request-id that’s being returned from the API. While generating thousands of those calls, this would be possible but the kind of work around I wouldn’t want to implement.

Request-Id in our case is used as follows:

  1. A user sends a request to the API (without any kind of id).
  2. The API assigns a request-id to this call.
  3. The request-id is used internally to correlate all subsequent sync and async actions that are triggered by that API call.
  4. The API responds to the user with the request-id in the header.

So the request-id is specific to a single call against our public API.
For us with access to the API’s telemetry, as well as the telemetry of correlated systems, the request-id would be immensely useful to trace what exactly happened due to a specific API call (timings in our backend systems, etc).

Would it be possible to write an extension for k6 to stream metrics to another service and could we access the response header within such an extension?
Ideally, I’d want all the metrics and logs to make their way into Azure Data Explorer in the end (another argument for writing my own metric streaming extension maybe?) although this could be a manual ingestion as well.

Hi Felix,

Thanks for clarifying the use case. Allow me some time to discuss the case internally and see if there are any options that would work, like an extension.

I appreciate your patience :pray:

1 Like

Hi Felix,

The scenario you have seems one that could potentially benefit from How to correlate performance testing and distributed tracing to proactively improve reliability. Though since you have your own ID in the response / telemetry, and want to visualize with Azure Data Explorer, I understand you’d want to go a different route.

Discussing this internally with @olegbespalov, we think that it’s possible that wrapping into custom metrics is less work than writing extensions.

If you want to go with extensions, the one that is a bit close to what you need and could serve as a basis is https://github.com/grafana/xk6-distributed-tracing/blob/master/client/client.go#L139-L148.

And you could for sure also create custom output to stream the metrics to the storage you need, like Azure Data Explorer.

Cheers!