opentracing/opentracing-javascript

How to I chain traces over more than two processes?

reselbob opened this issue · 13 comments

Hi:

I am trying to chain a trace over more than two processes.

I have three services: Service_A, Service_B and Service_C.

I understand that to chain from Service_A to Service_B I do the following in psuedo-code

Service_A
parentSpanContext = tracer.extract(FORMAT_HTTP_HEADERS, request.headers)

then I do an injection in Service_A to get it to Service_B:

    const span = tracer.startSpan('call_service', { childOf: root_span.context() });
    tracer.inject(span, FORMAT_HTTP_HEADERS, {});

This works just fine. However, chaining the trace for Service_B to Service_C is a problem for me.

In Service_B I get the parentSpanContext, like so

parentSpanContext = tracer.extract(FORMAT_HTTP_HEADERS, request.headers)

And to forward the chain onto Service_C, I think I do the following injection in Service_B:

tracer.inject(span, FORMAT_HTTP_HEADERS, request.headers);

Where I think I should add the request.headers of Service_B to inject() to keep the chain going.

Yet, when I look at the trace in Jaeger, the spans in Service_A have an association with Service_B. Yet, the Service_B spans do not have have an association with Service_C. However, the Service_C spans do appear distinctly.

Here is the actual code for Service_B. It's the burgerqueen service.

https://github.com/reselbob/dockerdemos/blob/add_tracing_to_foodcourt/foodcourt/burgerqueen/index.js

I must be doing something very wrong. Any help will be greatly appreciated.

From the code you linked, you're making a downstream call using await axios.post, which has no relation to the headers you previously populate. Also, you're using headers from the original request to the service, but you should be creating a new request for the downstream call and injecting trace context into the new request's headers.

Thank you, let me try. BTW: I just viewed you on YouTube here: https://youtu.be/fjYAU3jayVo It's very inspiring work!

@yurishkuro

injecting trace context

Do you mean span.context() which I see here: https://opentracing-javascript.surge.sh/classes/span.html#context

Also, if I may ask a favor, do you have an example of which attribute I should use to attach the context to the request.headers?

Is this the correct way as I show with header below. Or should the new request header simply be {}?

const callPaymentService = async (payload, root_span, request) => {
    const service = 'payments';
    const url = `http://${service}:${port}`;
    const span = tracer.startSpan('call_service', { childOf: root_span.context() });
    const headers = { 
        context: root_span.context()
      };
    tracer.inject(span, FORMAT_HTTP_HEADERS, headers);
    await axios.post(url, payload, {headers})
        .then(res => {
            span.setTag(Tags.HTTP_STATUS_CODE, 200)
            span.setTag('service_call_result', res.data)
            span.finish();
            return res.data;
        }, e => {
            span.setTag(Tags.ERROR, true)
            span.log({
                'event': 'error',
                'error.object': e
            });
            span.finish();
        });
};

Not to wear out my welcome, @yurishkuro, but I still can't get the trace to propagate:

Here is the client code here: https://github.com/reselbob/dockerdemos/blob/add_tracing_to_foodcourt/foodcourt/burgerqueen/index.js

const callPaymentService = async (payload, root_span, request) => {
    const service = 'payments';
    const url = `http://${service}:${port}`;
    const span = tracer.startSpan('call_service', { childOf: root_span.context() });
    span.setTag(Tags.SPAN_KIND, Tags.SPAN_KIND_RPC_CLIENT)
    tracer.inject(span.context(), FORMAT_HTTP_HEADERS, {});
    await axios.post(url, payload, {})
        .then(res => {
            span.setTag(Tags.HTTP_STATUS_CODE, 200)
            span.setTag('service_call_result', res.data)
            span.finish();
            return res.data;
        }, e => {
            span.setTag(Tags.ERROR, true)
            span.log({
                'event': 'error',
                'error.object': e
            });
            span.finish();
        });
};

And here is the receiving code here: https://github.com/reselbob/dockerdemos/blob/add_tracing_to_foodcourt/foodcourt/payments/index.js

const server = http.createServer((request, response) => {
    parentSpanContext = tracer.extract(FORMAT_HTTP_HEADERS, request.headers)
    const span = tracer.startSpan('payments_service_request', {
        childOf: parentSpanContext,
        tags: { [Tags.SPAN_KIND]: Tags.SPAN_KIND_RPC_SERVER }
    });

    let body = '';
    if (request.method.toUpperCase() === 'POST') {

        request.on('data', chunk => {
            body += chunk.toString(); // convert Buffer to string
        });
        request.on('end', () => {
           const payment = JSON.parse(body);
           payment .status = 'PAID';
           payment.transactionId = uuidv4();

            span.setTag('payment', payment);
            span.log({
                'event': 'payment_service_request',
                'value': payment
            });

            const str = JSON.stringify(payment);

            span.setTag(Tags.HTTP_STATUS_CODE, 200)
            span.setTag('payment_call_result', str)

            response.setHeader("Content-Type", "application/json");
            response.writeHead(200);
            response.end(str);

            span.finish();
        });
    }
}).listen(port, (err) => {
    console.log(`${service} API Server is started on ${port}  at ${new Date()} with pid ${process.pid}`);
});

Again, the spans show up in separate traces, but I can get the client to bind to the receiver is the code as shown about.

My sincerest thanks for any help you can provide.

tracing

When you call tracer.inject(span.context(), FORMAT_HTTP_HEADERS, something);, that something is better be part of the request you are making to another service. You've shown examples where something is request.headers but not used in the subsequent POST, or in the last comment something=={}, i.e. completely throw-away. Inject call populates something with trace metadata that must be received by the callee.

Did you try following https://github.com/yurishkuro/opentracing-tutorial ?

Yes, I read the Node.js tutorial, lessons 3 and 4. That calls across to two processes, publisher and formatted. This example goes one level deep, as evident by a header value of {} in http_get(). I've gotten a one-level client to server call working.

const method = 'GET';
    const headers = {};
    
    span.setTag(Tags.HTTP_URL, url);
    span.setTag(Tags.HTTP_METHOD, method);
    span.setTag(Tags.SPAN_KIND, Tags.SPAN_KIND_RPC_CLIENT);
    // Send span context via request headers (parent id etc.)
    tracer.inject(span, FORMAT_HTTP_HEADERS, headers);

Where I am getting confused is how to populate the trace metadata into the subsequent call to the second server. I am unclear as to exactly what data goes into the header value of the call to the second server in my scenario. (I am trying to hop over three microservices is sequence.) Obviously, the empty JSON object is wrong in the example below.

    const header = {};
    await axios.post(url, payload, {});

As you point out, it should be something. I am just unclear as to what exactly that something looks like. Might you have an example?

I understand that my confusion and questions may be a bit annoying to someone as expert as yourself. But, I trust you understand I am trying really hard to understand and am very grateful for the help you've provided. If I've exhausted your patience, I understand completely.

Thank you sincerely.

This is fine:

const headers = {};
tracer.inject(span, FORMAT_HTTP_HEADERS, headers);

but then when you make a POST request, you need to use these headers. I am not familiar with axios to advise how that can be done; usually all HTTP frameworks allow you to set the headers for outgoing requests.

Yes, the issue is some foolishness going on in how I am setting the Axios call. I will post the solution in the morning. Thank you for your patience.

Thank you, @yurishkuro ! I wish there was a way I could contact you to send you a token of appreciation for your guidance and patience.

Screen Shot 2020-09-23 at 8 47 43 AM

Just bought you some more coffee. I need help with a similar problem using OpenTelemetry. Please, let me know if you need more coffee than I sent you. @yurishkuro .

Have you tried their chat https://gitter.im/open-telemetry/opentelemetry-node ? I can try to help, but I don't work with OTEL libraries.

Yes, I am there now. Not getting a response. Let me go over to the OpenTracing Node Library on GitHub and open an issue. There is something going on with how I configuring docker-compose.yml. As I said things work just fine when running on bare localhost.