From 5f733a98c186d32558a202e7948f0200ade6e4e0 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Thu, 4 Dec 2025 14:53:17 +0300 Subject: [PATCH 1/5] Adds rpc helper module --- src/woody_rpc_helper.erl | 81 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/woody_rpc_helper.erl diff --git a/src/woody_rpc_helper.erl b/src/woody_rpc_helper.erl new file mode 100644 index 0000000..ccf211b --- /dev/null +++ b/src/woody_rpc_helper.erl @@ -0,0 +1,81 @@ +-module(woody_rpc_helper). + +-include_lib("opentelemetry_api/include/opentelemetry.hrl"). + +-export([encode_rpc_context/2]). +-export([decode_rpc_context/1]). + +-export_type([t/0]). + +-type t() :: map(). + +-spec encode_rpc_context(woody_context:ctx(), otel_ctx:t()) -> t(). +encode_rpc_context(WoodyContext, OtelContext) -> + #{ + <<"woody">> => woody_context_to_opaque(WoodyContext), + <<"otel">> => pack_otel_stub(OtelContext) + }. + +-spec decode_rpc_context(t()) -> {woody_context:ctx(), otel_ctx:t()}. +decode_rpc_context(RpcContext) -> + {decode_woody_context(RpcContext), decode_otel_context(RpcContext)}. + +%% + +decode_woody_context(#{<<"woody">> := OpaqueWoodyContext}) -> + opaque_to_woody_context(OpaqueWoodyContext); +decode_woody_context(_) -> + woody_context:new(). + +decode_otel_context(#{<<"otel">> := PackedOtelContext}) -> + restore_otel_stub(otel_ctx:get_current(), PackedOtelContext); +decode_otel_context(_) -> + otel_ctx:get_current(). + +pack_otel_stub(Ctx) -> + case otel_tracer:current_span_ctx(Ctx) of + undefined -> + []; + #span_ctx{trace_id = TraceID, span_id = SpanID, trace_flags = TraceFlags} -> + [trace_id_to_binary(TraceID), span_id_to_binary(SpanID), TraceFlags] + end. + +trace_id_to_binary(TraceID) -> + {ok, EncodedTraceID} = otel_utils:format_binary_string("~32.16.0b", [TraceID]), + EncodedTraceID. + +span_id_to_binary(SpanID) -> + {ok, EncodedSpanID} = otel_utils:format_binary_string("~16.16.0b", [SpanID]), + EncodedSpanID. + +restore_otel_stub(Ctx, [TraceID, SpanID, TraceFlags]) -> + SpanCtx = otel_tracer:from_remote_span(binary_to_id(TraceID), binary_to_id(SpanID), TraceFlags), + otel_tracer:set_current_span(Ctx, SpanCtx); +restore_otel_stub(Ctx, _Other) -> + Ctx. + +binary_to_id(Opaque) when is_binary(Opaque) -> + binary_to_integer(Opaque, 16). + +woody_context_to_opaque(#{rpc_id := RPCID, meta := ContextMeta}) -> + [1, woody_rpc_id_to_opaque(RPCID), ContextMeta]; +woody_context_to_opaque(#{rpc_id := RPCID}) -> + [1, woody_rpc_id_to_opaque(RPCID)]. + +woody_rpc_id_to_opaque(#{span_id := SpanID, trace_id := TraceID, parent_id := ParentID}) -> + [SpanID, TraceID, ParentID]. + +opaque_to_woody_context([1, RPCID, ContextMeta]) -> + #{ + rpc_id => opaque_to_woody_rpc_id(RPCID), + meta => ContextMeta, + deadline => undefined + }; +opaque_to_woody_context([1, RPCID]) -> + #{ + rpc_id => opaque_to_woody_rpc_id(RPCID), + deadline => undefined + }. + +opaque_to_woody_rpc_id([SpanID, TraceID, ParentID]) -> + #{span_id => SpanID, trace_id => TraceID, parent_id => ParentID}. From 45235a9832d2ff7b0c1b28ef2c29d0d7b0b2eaea Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Thu, 4 Dec 2025 15:12:17 +0300 Subject: [PATCH 2/5] Bumps CI actions --- .github/workflows/erlang-checks.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/erlang-checks.yml b/.github/workflows/erlang-checks.yml index e468c84..bdb4c9f 100644 --- a/.github/workflows/erlang-checks.yml +++ b/.github/workflows/erlang-checks.yml @@ -18,7 +18,7 @@ jobs: thrift-version: ${{ steps.thrift-version.outputs.version }} steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - run: grep -v '^#' .env >> $GITHUB_ENV - id: otp-version run: echo "::set-output name=version::$OTP_VERSION" @@ -30,7 +30,7 @@ jobs: run: name: Run checks needs: setup - uses: valitydev/erlang-workflows/.github/workflows/erlang-parallel-build.yml@v1.0.16 + uses: valitydev/erlang-workflows/.github/workflows/erlang-parallel-build.yml@v1 with: otp-version: ${{ needs.setup.outputs.otp-version }} rebar-version: ${{ needs.setup.outputs.rebar-version }} From 904b887430f8b4d78fd8a58acfe105d11287a6f7 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Thu, 4 Dec 2025 15:26:27 +0300 Subject: [PATCH 3/5] Fixes typespec --- src/woody_rpc_helper.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/woody_rpc_helper.erl b/src/woody_rpc_helper.erl index ccf211b..f1dd281 100644 --- a/src/woody_rpc_helper.erl +++ b/src/woody_rpc_helper.erl @@ -5,18 +5,18 @@ -export([encode_rpc_context/2]). -export([decode_rpc_context/1]). --export_type([t/0]). +-export_type([rpc_context/0]). --type t() :: map(). +-type rpc_context() :: map(). --spec encode_rpc_context(woody_context:ctx(), otel_ctx:t()) -> t(). +-spec encode_rpc_context(woody_context:ctx(), otel_ctx:t()) -> rpc_context(). encode_rpc_context(WoodyContext, OtelContext) -> #{ <<"woody">> => woody_context_to_opaque(WoodyContext), <<"otel">> => pack_otel_stub(OtelContext) }. --spec decode_rpc_context(t()) -> {woody_context:ctx(), otel_ctx:t()}. +-spec decode_rpc_context(rpc_context()) -> {woody_context:ctx(), otel_ctx:t()}. decode_rpc_context(RpcContext) -> {decode_woody_context(RpcContext), decode_otel_context(RpcContext)}. From 5f0a357073b637aba63c53467342e97cf8f3a9e0 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Thu, 4 Dec 2025 18:23:31 +0300 Subject: [PATCH 4/5] Moves `otel_ctx` attachement to woody rpc helper --- src/woody_rpc_helper.erl | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/woody_rpc_helper.erl b/src/woody_rpc_helper.erl index f1dd281..327d9c5 100644 --- a/src/woody_rpc_helper.erl +++ b/src/woody_rpc_helper.erl @@ -1,9 +1,12 @@ -module(woody_rpc_helper). +%% TODO Add unit testcases to assert encode/decode and viable otel context selection + -include_lib("opentelemetry_api/include/opentelemetry.hrl"). -export([encode_rpc_context/2]). -export([decode_rpc_context/1]). +-export([attach_otel_context/1]). -export_type([rpc_context/0]). @@ -20,8 +23,27 @@ encode_rpc_context(WoodyContext, OtelContext) -> decode_rpc_context(RpcContext) -> {decode_woody_context(RpcContext), decode_otel_context(RpcContext)}. +-spec attach_otel_context(otel_ctx:t()) -> ok. +attach_otel_context(OtelContext) when is_map(OtelContext) andalso map_size(OtelContext) =:= 0 -> + ok; +attach_otel_context(OtelContext) when is_map(OtelContext) -> + _ = otel_ctx:attach(choose_viable_otel_ctx(OtelContext, otel_ctx:get_current())), + ok; +attach_otel_context(_) -> + ok. + %% +%% lowest bit flags if span is sampled +-define(IS_NOT_SAMPLED(SpanCtx), SpanCtx#span_ctx.trace_flags band 2#1 =/= 1). + +choose_viable_otel_ctx(NewCtx, CurrentCtx) -> + case {otel_tracer:current_span_ctx(NewCtx), otel_tracer:current_span_ctx(CurrentCtx)} of + {SpanCtx = #span_ctx{}, #span_ctx{}} when ?IS_NOT_SAMPLED(SpanCtx) -> CurrentCtx; + {undefined, #span_ctx{}} -> CurrentCtx; + {_, _} -> NewCtx + end. + decode_woody_context(#{<<"woody">> := OpaqueWoodyContext}) -> opaque_to_woody_context(OpaqueWoodyContext); decode_woody_context(_) -> From 43175ef5930a28c3e44ea7ad57d18665d0b8a01f Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Thu, 4 Dec 2025 18:30:15 +0300 Subject: [PATCH 5/5] Moves test case for otel ctx selection --- src/woody_rpc_helper.erl | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/woody_rpc_helper.erl b/src/woody_rpc_helper.erl index 327d9c5..314db80 100644 --- a/src/woody_rpc_helper.erl +++ b/src/woody_rpc_helper.erl @@ -101,3 +101,39 @@ opaque_to_woody_context([1, RPCID]) -> opaque_to_woody_rpc_id([SpanID, TraceID, ParentID]) -> #{span_id => SpanID, trace_id => TraceID, parent_id => ParentID}. + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +-type testgen() :: {_ID, fun(() -> _)}. +-spec test() -> _. + +-define(IS_SAMPLED, 1). +-define(NOT_SAMPLED, 0). +-define(OTEL_CTX(IsSampled), + otel_tracer:set_current_span( + otel_ctx:new(), + (otel_tracer_noop:noop_span_ctx())#span_ctx{ + trace_id = otel_id_generator:generate_trace_id(), + span_id = otel_id_generator:generate_span_id(), + is_valid = true, + is_remote = true, + is_recording = false, + trace_flags = IsSampled + } + ) +). + +-spec choose_viable_otel_ctx_test_() -> [testgen()]. +choose_viable_otel_ctx_test_() -> + A = ?OTEL_CTX(?IS_SAMPLED), + B = ?OTEL_CTX(?NOT_SAMPLED), + [ + ?_assertEqual(A, choose_viable_otel_ctx(A, B)), + ?_assertEqual(A, choose_viable_otel_ctx(B, A)), + ?_assertEqual(A, choose_viable_otel_ctx(A, otel_ctx:new())), + ?_assertEqual(B, choose_viable_otel_ctx(otel_ctx:new(), B)), + ?_assertEqual(otel_ctx:new(), choose_viable_otel_ctx(otel_ctx:new(), otel_ctx:new())) + ]. + +-endif.