Skip to content

Commit c1186ec

Browse files
committed
Improve tracing
1 parent 718b130 commit c1186ec

File tree

8 files changed

+247
-41
lines changed

8 files changed

+247
-41
lines changed

instrumentation/nginx/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ add_library(otel_ngx_module SHARED
2323
src/otel_ngx_module_modules.c
2424
src/propagate.cpp
2525
src/script.cpp
26+
src/post_batch_span_processor.cpp
27+
src/post_span_processor.cpp
2628
)
2729

2830
target_compile_options(otel_ngx_module

instrumentation/nginx/src/otel_ngx_module.cpp

Lines changed: 90 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ extern ngx_module_t otel_ngx_module;
2121
#include "nginx_config.h"
2222
#include "nginx_utils.h"
2323
#include "propagate.h"
24+
#include "post_span_processor.h"
25+
#include "post_batch_span_processor.h"
2426
#include <opentelemetry/context/context.h>
2527
#include <opentelemetry/nostd/shared_ptr.h>
2628
#include <opentelemetry/sdk/trace/batch_span_processor.h>
@@ -496,21 +498,13 @@ TraceContext* CreateTraceContext(ngx_http_request_t* req, ngx_http_variable_valu
496498
return context;
497499
}
498500

499-
ngx_int_t StartNgxSpan(ngx_http_request_t* req) {
500-
if (!IsOtelEnabled(req)) {
501-
return NGX_DECLINED;
502-
}
503-
504-
// Internal requests must be called from another location in nginx, that should already have a trace. Without this check, a call would generate an extra (unrelated) span without much information
505-
if (req->internal) {
506-
return NGX_DECLINED;
507-
}
501+
TraceContext* StartNgxTrace(ngx_http_request_t* req) {
508502

509503
ngx_http_variable_value_t* val = ngx_http_get_indexed_variable(req, otel_ngx_variables[0].index);
510504

511505
if (!val) {
512506
ngx_log_error(NGX_LOG_ERR, req->connection->log, 0, "Unable to find OpenTelemetry context");
513-
return NGX_DECLINED;
507+
return nullptr;
514508
}
515509

516510
TraceContext* context = CreateTraceContext(req, val);
@@ -524,30 +518,43 @@ ngx_int_t StartNgxSpan(ngx_http_request_t* req) {
524518
incomingContext = ExtractContext(&carrier);
525519
}
526520

527-
trace::StartSpanOptions startOpts;
528-
startOpts.kind = trace::SpanKind::kServer;
529-
startOpts.parent = GetCurrentSpan(incomingContext);
521+
const auto operationName = GetOperationName(req);
522+
523+
std::initializer_list<std::pair<nostd::string_view, common::AttributeValue>> commonAttributes = {
524+
{"http.method", FromNgxString(req->method_name)},
525+
{"http.flavor", NgxHttpFlavor(req)},
526+
{"http.target", FromNgxString(req->unparsed_uri)},
527+
// Add http.route as its according to OpenTelemetry spec
528+
{"http.route", FromNgxString(req->unparsed_uri)}
529+
};
530+
531+
trace::StartSpanOptions requestStartOpts;
532+
requestStartOpts.parent = GetCurrentSpan(incomingContext);
533+
requestStartOpts.kind = trace::SpanKind::kServer;
534+
context->request_span = GetTracer()->StartSpan(operationName,commonAttributes, requestStartOpts);
530535

531-
context->request_span = GetTracer()->StartSpan(
532-
GetOperationName(req),
533-
{
534-
{"http.method", FromNgxString(req->method_name)},
535-
{"http.flavor", NgxHttpFlavor(req)},
536-
{"http.target", FromNgxString(req->unparsed_uri)},
537-
},
538-
startOpts);
536+
trace::StartSpanOptions innerSpanOpts;
537+
innerSpanOpts.parent = context->request_span->GetContext();
538+
innerSpanOpts.kind = trace::SpanKind::kInternal;
539+
540+
context->inner_span = GetTracer()->StartSpan(operationName,commonAttributes,innerSpanOpts);
539541

540542
nostd::string_view serverName = GetNgxServerName(req);
541543
if (!serverName.empty()) {
542544
context->request_span->SetAttribute("http.server_name", serverName);
545+
context->inner_span->SetAttribute("http.server_name", serverName);
543546
}
544547

545548
if (req->headers_in.host) {
546-
context->request_span->SetAttribute("http.host", FromNgxString(req->headers_in.host->value));
549+
const auto host = FromNgxString(req->headers_in.host->value);
550+
context->request_span->SetAttribute("http.host", host);
551+
context->inner_span->SetAttribute("http.host", host);
547552
}
548553

549554
if (req->headers_in.user_agent) {
550-
context->request_span->SetAttribute("http.user_agent", FromNgxString(req->headers_in.user_agent->value));
555+
const auto userAgent = FromNgxString(req->headers_in.user_agent->value);
556+
context->request_span->SetAttribute("http.user_agent", userAgent);
557+
context->inner_span->SetAttribute("http.user_agent", userAgent);
551558
}
552559

553560
if (locConf->captureHeaders) {
@@ -559,10 +566,24 @@ ngx_int_t StartNgxSpan(ngx_http_request_t* req) {
559566
{excludedHeaders, 2});
560567
}
561568

562-
auto outgoingContext = incomingContext.SetValue(trace::kSpanKey, context->request_span);
569+
auto outgoingContext = incomingContext.SetValue(trace::kSpanKey, context->inner_span);
563570

564571
InjectContext(&carrier, outgoingContext);
565572

573+
return context;
574+
}
575+
576+
ngx_int_t StartNgxSpan(ngx_http_request_t* req) {
577+
if (!IsOtelEnabled(req)) {
578+
return NGX_DECLINED;
579+
}
580+
581+
if (req->internal) {
582+
return NGX_DECLINED;
583+
}
584+
585+
StartNgxTrace(req);
586+
566587
return NGX_DECLINED;
567588
}
568589

@@ -591,18 +612,7 @@ void AddScriptAttributes(
591612
}
592613
}
593614

594-
ngx_int_t FinishNgxSpan(ngx_http_request_t* req) {
595-
if (!IsOtelEnabled(req)) {
596-
return NGX_DECLINED;
597-
}
598-
599-
TraceContext* context = GetTraceContext(req);
600-
601-
if (!context) {
602-
return NGX_DECLINED;
603-
}
604-
605-
auto span = context->request_span;
615+
void UpdateSpan(nostd::shared_ptr<opentelemetry::trace::Span> span, ngx_http_request_t* req, bool captureHeaders = false) {
606616
const auto status_code = req->headers_out.status;
607617
span->SetAttribute("http.status_code", status_code);
608618

@@ -612,9 +622,9 @@ ngx_int_t FinishNgxSpan(ngx_http_request_t* req) {
612622
span->SetStatus(trace::StatusCode::kOk);
613623
}
614624

615-
OtelNgxLocationConf* locConf = GetOtelLocationConf(req);
625+
OtelNgxLocationConf *locConf = GetOtelLocationConf(req);
616626

617-
if (locConf->captureHeaders) {
627+
if (locConf->captureHeaders && captureHeaders) {
618628
OtelCaptureHeaders(span, ngx_string("http.response.header."), &req->headers_out.headers,
619629
#if (NGX_PCRE)
620630
locConf->sensitiveHeaderNames, locConf->sensitiveHeaderValues,
@@ -625,7 +635,46 @@ ngx_int_t FinishNgxSpan(ngx_http_request_t* req) {
625635
AddScriptAttributes(span.get(), GetOtelMainConf(req)->scriptAttributes, req);
626636
AddScriptAttributes(span.get(), locConf->customAttributes, req);
627637

638+
if (req->headers_out.status >= 400 && req->headers_out.status <= 599) {
639+
span->SetStatus(trace::StatusCode::kError);
640+
}
641+
628642
span->UpdateName(GetOperationName(req));
643+
}
644+
645+
ngx_int_t FinishNgxSpan(ngx_http_request_t* req) {
646+
if (!IsOtelEnabled(req)) {
647+
return NGX_DECLINED;
648+
}
649+
650+
if (req->internal) {
651+
return NGX_DECLINED;
652+
}
653+
654+
TraceContext* context = GetTraceContext(req);
655+
656+
/*
657+
* When nginx fails to process request (bad request, prematurely closed) then span is not started, here we can start
658+
* a new span because there were no upstream calls anyway.
659+
*/
660+
if (!context) {
661+
context = StartNgxTrace(req);
662+
}
663+
664+
auto span = context->request_span;
665+
UpdateSpan(span, req, true);
666+
667+
if (context->inner_span) {
668+
669+
const bool hasUpstream = req->upstream != nullptr;
670+
if (hasUpstream) {
671+
context->inner_span->SetAttribute("span.kind", static_cast<int>(trace::SpanKind::kClient));
672+
}
673+
674+
UpdateSpan(context->inner_span, req);
675+
676+
context->inner_span->End();
677+
}
629678

630679
span->End();
631680
return NGX_DECLINED;
@@ -642,7 +691,7 @@ static ngx_int_t InitModule(ngx_conf_t* conf) {
642691

643692
const PhaseHandler handlers[] = {
644693
{NGX_HTTP_REWRITE_PHASE, StartNgxSpan},
645-
{NGX_HTTP_LOG_PHASE, FinishNgxSpan},
694+
{NGX_HTTP_LOG_PHASE, FinishNgxSpan}
646695
};
647696

648697
for (const PhaseHandler& ph : handlers) {
@@ -1300,11 +1349,11 @@ CreateProcessor(const OtelNgxAgentConfig* conf, std::unique_ptr<sdktrace::SpanEx
13001349
opts.max_export_batch_size = conf->processor.batch.maxExportBatchSize;
13011350

13021351
return std::unique_ptr<sdktrace::SpanProcessor>(
1303-
new sdktrace::BatchSpanProcessor(std::move(exporter), opts));
1352+
new PostBatchSpanProcessor(std::move(exporter), opts));
13041353
}
13051354

13061355
return std::unique_ptr<sdktrace::SpanProcessor>(
1307-
new sdktrace::SimpleSpanProcessor(std::move(exporter)));
1356+
new PostSpanProcessor(std::move(exporter)));
13081357
}
13091358

13101359
static std::unique_ptr<sdktrace::Sampler> CreateSampler(const OtelNgxAgentConfig* conf) {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include "post_batch_span_processor.h"
2+
#include <opentelemetry/exporters/otlp/otlp_recordable.h>
3+
4+
void PostBatchSpanProcessor::OnEnd(std::unique_ptr<opentelemetry::sdk::trace::Recordable> &&span) noexcept
5+
{
6+
ProxyRecordable* proxy = static_cast<ProxyRecordable*>(span.get());
7+
BatchSpanProcessor::OnEnd(std::move(proxy->GetRealRecordable()));
8+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#ifndef OPENTELEMETRY_NGINX_POST_BATCH_SPAN_PROCESSOR_H
2+
#define OPENTELEMETRY_NGINX_POST_BATCH_SPAN_PROCESSOR_H
3+
4+
#include "proxy_recordable.h"
5+
#include <opentelemetry/nostd/shared_ptr.h>
6+
#include <opentelemetry/sdk/trace/batch_span_processor.h>
7+
8+
namespace trace = opentelemetry::trace;
9+
namespace nostd = opentelemetry::nostd;
10+
namespace sdktrace = opentelemetry::sdk::trace;
11+
12+
class PostBatchSpanProcessor : public sdktrace::BatchSpanProcessor {
13+
public:
14+
15+
explicit PostBatchSpanProcessor(std::unique_ptr<sdktrace::SpanExporter> &&exporter,
16+
const sdktrace::BatchSpanProcessorOptions &options) noexcept
17+
: BatchSpanProcessor(std::move(exporter), options)
18+
{}
19+
20+
void OnEnd(std::unique_ptr<opentelemetry::sdk::trace::Recordable> &&span) noexcept override;
21+
22+
std::unique_ptr<opentelemetry::sdk::trace::Recordable> MakeRecordable() noexcept override {
23+
return std::unique_ptr<ProxyRecordable>(new ProxyRecordable());
24+
}
25+
};
26+
27+
#endif //OPENTELEMETRY_NGINX_POST_BATCH_SPAN_PROCESSOR_H
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#include "post_span_processor.h"
2+
3+
void PostSpanProcessor::OnEnd(std::unique_ptr<opentelemetry::sdk::trace::Recordable> &&span) noexcept
4+
{
5+
ProxyRecordable* proxy = static_cast<ProxyRecordable*>(span.get());
6+
SimpleSpanProcessor::OnEnd(std::move(proxy->GetRealRecordable()));
7+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#ifndef OPENTELEMETRY_NGINX_POST_SPAN_PROCESSOR_H
2+
#define OPENTELEMETRY_NGINX_POST_SPAN_PROCESSOR_H
3+
4+
#include <opentelemetry/nostd/shared_ptr.h>
5+
#include <opentelemetry/sdk/trace/simple_processor.h>
6+
#include "proxy_recordable.h"
7+
8+
namespace trace = opentelemetry::trace;
9+
namespace nostd = opentelemetry::nostd;
10+
namespace sdktrace = opentelemetry::sdk::trace;
11+
12+
class PostSpanProcessor : public sdktrace::SimpleSpanProcessor {
13+
public:
14+
15+
explicit PostSpanProcessor(std::unique_ptr<sdktrace::SpanExporter> &&exporter) noexcept
16+
: SimpleSpanProcessor(std::move(exporter))
17+
{}
18+
19+
void OnEnd(std::unique_ptr<opentelemetry::sdk::trace::Recordable> &&span) noexcept override;
20+
21+
std::unique_ptr<opentelemetry::sdk::trace::Recordable> MakeRecordable() noexcept override {
22+
return std::unique_ptr<ProxyRecordable>(new ProxyRecordable());
23+
}
24+
};
25+
26+
#endif //OPENTELEMETRY_NGINX_POST_SPAN_PROCESSOR_H
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#ifndef OPENTELEMETRY_NGINX_PROXY_RECORDABLE_H
2+
#define OPENTELEMETRY_NGINX_PROXY_RECORDABLE_H
3+
4+
#include <opentelemetry/exporters/otlp/otlp_recordable.h>
5+
6+
namespace trace = opentelemetry::trace;
7+
namespace nostd = opentelemetry::nostd;
8+
namespace sdktrace = opentelemetry::sdk::trace;
9+
10+
/**
11+
* Wraps actual recordable object so we could override span.kind through SetAttribute. This workarounds the problem that
12+
* in OpenTelemetry cpp SDK span kind is immutable on a span, but it causes problems for this instrumentation library.
13+
*/
14+
class ProxyRecordable : public sdktrace::Recordable {
15+
public:
16+
17+
ProxyRecordable()
18+
: _realRecordable(new opentelemetry::exporter::otlp::OtlpRecordable()) { }
19+
20+
std::unique_ptr<opentelemetry::exporter::otlp::OtlpRecordable> GetRealRecordable() {
21+
return std::unique_ptr<opentelemetry::exporter::otlp::OtlpRecordable>(std::move(_realRecordable));
22+
}
23+
24+
void SetIdentity(const opentelemetry::trace::SpanContext &span_context,
25+
opentelemetry::trace::SpanId parent_span_id) noexcept override {
26+
_realRecordable->SetIdentity(span_context, parent_span_id);
27+
}
28+
29+
virtual void SetAttribute(nostd::string_view key,
30+
const opentelemetry::common::AttributeValue &value) noexcept override {
31+
if (key == "span.kind") {
32+
trace::SpanKind kind = static_cast<trace::SpanKind>(nostd::get<int>(value));
33+
_realRecordable->SetSpanKind(kind);
34+
} else {
35+
_realRecordable->SetAttribute(key, value);
36+
}
37+
}
38+
39+
void AddEvent(nostd::string_view name,
40+
opentelemetry::common::SystemTimestamp timestamp,
41+
const opentelemetry::common::KeyValueIterable &attributes) noexcept override {
42+
_realRecordable->AddEvent(name, timestamp, attributes);
43+
};
44+
45+
void AddLink(const opentelemetry::trace::SpanContext &span_context,
46+
const opentelemetry::common::KeyValueIterable &attributes) noexcept override {
47+
_realRecordable->AddLink(span_context, attributes);
48+
};
49+
50+
void SetStatus(opentelemetry::trace::StatusCode code,
51+
nostd::string_view description) noexcept override {
52+
_realRecordable->SetStatus(code, description);
53+
};
54+
55+
void SetName(nostd::string_view name) noexcept override {
56+
_realRecordable->SetName(name);
57+
};
58+
59+
void SetSpanKind(opentelemetry::trace::SpanKind span_kind) noexcept override {
60+
_realRecordable->SetSpanKind(span_kind);
61+
};
62+
63+
void SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept override {
64+
_realRecordable->SetResource(resource);
65+
};
66+
67+
void SetStartTime(opentelemetry::common::SystemTimestamp start_time) noexcept override {
68+
_realRecordable->SetStartTime(start_time);
69+
};
70+
71+
void SetDuration(std::chrono::nanoseconds duration) noexcept override {
72+
_realRecordable->SetDuration(duration);
73+
};
74+
75+
void SetInstrumentationScope(const sdktrace::InstrumentationScope &instrumentation_scope) noexcept override {
76+
_realRecordable->SetInstrumentationScope(instrumentation_scope);
77+
};
78+
79+
private:
80+
std::unique_ptr<opentelemetry::exporter::otlp::OtlpRecordable> _realRecordable;
81+
};
82+
83+
#endif //OPENTELEMETRY_NGINX_PROXY_RECORDABLE_H

instrumentation/nginx/src/trace_context.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ struct TraceContext {
2222
TraceContext(ngx_http_request_t* req) : request(req), traceHeader{} {}
2323
/* The current request being handled by nginx. */
2424
ngx_http_request_t* request;
25+
/* Span used to record incoming requests */
2526
opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span> request_span;
27+
/* Span used to record internal processing. This span could be internal or client
28+
* depending if nginx handles request locally or calls upstream */
29+
opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span> inner_span;
2630
/* Headers to be injected for the upstream request. */
2731
TraceHeader traceHeader[2];
2832
};

0 commit comments

Comments
 (0)