From db5be634eca7f1a278205dff81a997e22986a634 Mon Sep 17 00:00:00 2001 From: Sam Handler Date: Thu, 11 Aug 2022 09:03:40 -0600 Subject: [PATCH 1/5] add baggage module --- lib/opentelemetry/baggage.lua | 75 +++++++++++++++++++++++++++++++++++ spec/baggage_spec.lua | 33 +++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 lib/opentelemetry/baggage.lua create mode 100644 spec/baggage_spec.lua diff --git a/lib/opentelemetry/baggage.lua b/lib/opentelemetry/baggage.lua new file mode 100644 index 0000000..8aab093 --- /dev/null +++ b/lib/opentelemetry/baggage.lua @@ -0,0 +1,75 @@ +local context = require("opentelemetry.context") +local util = require("opentelemetry.util") + +local _M = { +} + +local mt = { + __index = _M +} + +local BAGGAGE_KEY = "opentelemetry-baggage" + +function _M.new(values) + return setmetatable({ values = values or {} }, mt) +end + +-------------------------------------------------------------------------------- +-- Set a value in a baggage instance. Does _not_ inject into context +-- +-- @name name for which to set the value in baggage +-- @value value to set must be string +-- @metadata metadata to set in baggage (string) +-- @return baggage +-------------------------------------------------------------------------------- +function _M.set_value(self, name, value, metadata) + local new_values = util.shallow_copy_table(self.values) + new_values[name] = { value = value, metadata = metadata } + return self.new(new_values) +end + +-------------------------------------------------------------------------------- +-- Get value stored at a specific name in a baggage instance +-- +-- @name name for which to set the value in baggage +-- @return baggage +-------------------------------------------------------------------------------- +function _M.get_value(self, name) + if self.values[name] then + return self.values[name].value + else + return nil + end +end + + +-------------------------------------------------------------------------------- +-- Remove value stored at a specific name in a baggage instance +-- +-- @name name to remove from baggage +-- @return baggage +-------------------------------------------------------------------------------- +function _M.remove_value(self, name) + local new_values = util.shallow_copy_table(self.values) + new_values[name] = nil + return self.new(new_values) +end + + +-------------------------------------------------------------------------------- +-- Get all values in a baggage instance +-- +-- @context context from which to access the baggage (defaults to current context) +-- @return table like { keyname = { value = "value", metadata = "metadatastring"} } +-------------------------------------------------------------------------------- +function _M.get_all_values(self) + return self.values +end + +local function baggage_for(ctx) + ctx = ctx or context:current() + local vals = ctx:get(BAGGAGE_KEY) + _M.new(vals) +end + +return _M diff --git a/spec/baggage_spec.lua b/spec/baggage_spec.lua new file mode 100644 index 0000000..31aaab9 --- /dev/null +++ b/spec/baggage_spec.lua @@ -0,0 +1,33 @@ +local baggage = require("opentelemetry.baggage") + +describe("set_value and get_value", function() + it("sets a value and returns baggage instance", function() + local baggage = baggage.new({ oldkey = { value = "wat" } }) + local new_baggage = baggage:set_value("keyname", "val", "metadatastring") + assert.are.equal(new_baggage:get_value("keyname"), "val") + assert.are.equal(new_baggage:get_value("oldkey"), "wat") + end) + + it("overwrites keys", function() + local baggage = baggage.new({ oldkey = { value = "wat" } }) + local new_baggage = baggage:set_value("oldkey", "newvalue") + assert.are.equal(new_baggage:get_value("oldkey"), "newvalue") + end) +end) + +describe("get_values", function() + it("returns all values", function() + local values = { key1 = { value = "wat" }, key2 = { value = "wat2" } } + local baggage = baggage.new(values) + assert.are.same(baggage:get_all_values(), values) + end) +end) + +describe("remove_value", function() + it("returns new baggage instance without value", function() + local values = { key1 = { value = "wat" }, key2 = { value = "wat2" } } + local baggage = baggage.new(values) + local new_baggage = baggage:remove_value("key1") + assert.are.same(new_baggage:get_all_values(), { key2 = { value = "wat2" } }) + end) +end) From 0a0fcdde1d11f61be816d7b29388a92f6a5bc90e Mon Sep 17 00:00:00 2001 From: Sam Handler Date: Thu, 11 Aug 2022 12:26:03 -0600 Subject: [PATCH 2/5] functions for injecting/extracting baggage to context --- lib/opentelemetry/context.lua | 21 ++++++++++++++++++++- spec/context_spec.lua | 10 ++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/opentelemetry/context.lua b/lib/opentelemetry/context.lua index d9993ce..f981f03 100644 --- a/lib/opentelemetry/context.lua +++ b/lib/opentelemetry/context.lua @@ -11,7 +11,7 @@ local mt = { } local context_key = "__opentelemetry_context__" - +local baggage_context_key = "__opentelemetry_baggage__" -------------------------------------------------------------------------------- -- Create new context with set of entries @@ -92,6 +92,25 @@ function _M.set(self, key, value) return self.new(vals, self.sp) end +-------------------------------------------------------------------------------- +-- Inject baggage into current context +-- +-- @baggage baggage instance to inject +-- @return context +-------------------------------------------------------------------------------- +function _M.inject_baggage(self, baggage) + return self:set(baggage_context_key, baggage) +end + +-------------------------------------------------------------------------------- +-- Extract baggage from context +-- +-- @return baggage +-------------------------------------------------------------------------------- +function _M.extract_baggage(self) + return self:get(baggage_context_key) +end + function _M.with_span(self, span) return self.new({}, span) end diff --git a/spec/context_spec.lua b/spec/context_spec.lua index e7d9920..a1554c8 100644 --- a/spec/context_spec.lua +++ b/spec/context_spec.lua @@ -1,5 +1,6 @@ local context = require("opentelemetry.context") local otel_global = require("opentelemetry.global") +local baggage = require("opentelemetry.baggage") describe("get and set", function() it("stores and retrieves values at given key", function() @@ -62,3 +63,12 @@ describe("detach", function() assert.is_same("Token does not match (1 context entries in stack, token provided was 2).", err) end) end) + +describe("inject and extract baggage", function() + it("adds baggage to context and extracts it", function() + local ctx = context.new(storage) + local baggage = baggage.new({ key1 = { value = "wat" } }) + local new_ctx = ctx:inject_baggage(baggage) + assert.are.same(new_ctx:extract_baggage(), baggage) + end) +end) From 1c21c40f9d5861b97ad4c5dbb47b39cbfac4b728 Mon Sep 17 00:00:00 2001 From: Sam Handler Date: Fri, 12 Aug 2022 14:59:25 -0600 Subject: [PATCH 3/5] add rudimentary baggage propagator --- lib/opentelemetry/baggage.lua | 24 ++-- .../text_map/baggage_propagator.lua | 123 ++++++++++++++++++ lib/opentelemetry/context.lua | 3 +- lib/opentelemetry/util.lua | 36 +++++ .../text_map/baggage_propagator_spec.lua | 122 +++++++++++++++++ spec/baggage_spec.lua | 4 +- 6 files changed, 293 insertions(+), 19 deletions(-) create mode 100644 lib/opentelemetry/baggage/propagation/text_map/baggage_propagator.lua create mode 100644 spec/baggage/propagation/text_map/baggage_propagator_spec.lua diff --git a/lib/opentelemetry/baggage.lua b/lib/opentelemetry/baggage.lua index 8aab093..37fc041 100644 --- a/lib/opentelemetry/baggage.lua +++ b/lib/opentelemetry/baggage.lua @@ -1,4 +1,3 @@ -local context = require("opentelemetry.context") local util = require("opentelemetry.util") local _M = { @@ -8,8 +7,6 @@ local mt = { __index = _M } -local BAGGAGE_KEY = "opentelemetry-baggage" - function _M.new(values) return setmetatable({ values = values or {} }, mt) end @@ -42,9 +39,8 @@ function _M.get_value(self, name) end end - -------------------------------------------------------------------------------- --- Remove value stored at a specific name in a baggage instance +-- Remove value stored at a specific name in a baggage instance. -- -- @name name to remove from baggage -- @return baggage @@ -55,21 +51,17 @@ function _M.remove_value(self, name) return self.new(new_values) end - -------------------------------------------------------------------------------- --- Get all values in a baggage instance +-- Get all values in a baggage instance. This is supposed to return an immutable +-- collection, but we just return a copy of the table stored at values. -- --- @context context from which to access the baggage (defaults to current context) --- @return table like { keyname = { value = "value", metadata = "metadatastring"} } +-- @context context from which to access the baggage (defaults to +-- current context) +-- @return table like { keyname = { value = "value", +-- metadata = "metadatastring"} } -------------------------------------------------------------------------------- function _M.get_all_values(self) - return self.values -end - -local function baggage_for(ctx) - ctx = ctx or context:current() - local vals = ctx:get(BAGGAGE_KEY) - _M.new(vals) + return util.shallow_copy_table(self.values) end return _M diff --git a/lib/opentelemetry/baggage/propagation/text_map/baggage_propagator.lua b/lib/opentelemetry/baggage/propagation/text_map/baggage_propagator.lua new file mode 100644 index 0000000..4642711 --- /dev/null +++ b/lib/opentelemetry/baggage/propagation/text_map/baggage_propagator.lua @@ -0,0 +1,123 @@ +-------------------------------------------------------------------------------- +-- +-- See https://w3c.github.io/baggage/ for details. +-- +-------------------------------------------------------------------------------- + +local baggage = require("opentelemetry.baggage") +local text_map_getter = require("opentelemetry.trace.propagation.text_map.getter") +local text_map_setter = require("opentelemetry.trace.propagation.text_map.setter") +local util = require("opentelemetry.util") + +local _M = { +} + +local mt = { + __index = _M, +} + +local baggage_header = "baggage" + +function _M.new() + return setmetatable( + { + text_map_setter = text_map_setter.new(), + text_map_getter = text_map_getter.new() + }, mt) +end + +-------------=------------------------------------------------------------------ +-- Set baggage header on outbound HTTP request header. +-- +-- @param context context storage +-- @param carrier nginx request +-- @param setter setter for interacting with carrier +-- @return nil +-------------------------------------------------------------------------------- +function _M:inject(context, carrier, setter) + setter = setter or self.text_map_setter + local bgg = context:extract_baggage() + local header_string = "" + for k, v in pairs(bgg.values) do + local element = k .. "=" .. v.value + if v.metadata then + element = element .. ";" .. v.metadata + end + element = element .. "," + header_string = header_string .. element + end + + -- trim trailing comma + header_string = header_string:sub(0, -2) + + setter.set( + carrier, + baggage_header, + util.percent_encode_baggage_string(header_string) + ) +end + +-------------------------------------------------------------------------------- +-- Extract baggage from HTTP request headers. +-- +-- @context current context +-- @carrier ngx.req +-- @return new context with baggage associated +-------------------------------------------------------------------------------- +function _M:extract(context, carrier, getter) + getter = getter or self.text_map_getter + local baggage_string = getter.get(carrier, baggage_header) + if not baggage_string then + return context.new(context.entries) + else + baggage_string = util.decode_percent_encoded_string(baggage_string) + end + + local baggage_entries = {} + -- split apart string on comma and build up baggage entries + for list_member in string.gmatch(baggage_string, "([^,]+)") do + -- extract metadata from each list member + local kv, metadata = string.match(list_member, "([^;]+);(.*)") + + -- If there's no semicolon in list member, then kv and metadata are nil + -- and we need to correct that + if not kv then + kv = list_member + metadata = "" + end + + -- split apart k/v on equals sign + for k, v in string.gmatch(kv, "(.+)=(.+)") do + if self.validate_baggage(k, v) then + baggage_entries[k] = { value = v, metadata = metadata } + else + ngx.log(ngx.WARN, "invalid baggage entry: " .. k .. "=" .. v) + end + end + end + + local extracted_baggage = baggage.new(baggage_entries) + return context:inject_baggage(extracted_baggage) +end + +-------------------------------------------------------------------------------- +-- Check to see if baggage has both key and value component +-- +-- @key baggage key +-- @value baggage value +-- @return boolean +-------------------------------------------------------------------------------- +function _M.validate_baggage(key, value) + return (key and value) ~= nil +end + +-------------------------------------------------------------------------------- +-- Fields that will be used by the propagator +-- +-- @return table +-------------------------------------------------------------------------------- +function _M.fields() + return { "baggage" } +end + +return _M diff --git a/lib/opentelemetry/context.lua b/lib/opentelemetry/context.lua index f981f03..e43281d 100644 --- a/lib/opentelemetry/context.lua +++ b/lib/opentelemetry/context.lua @@ -1,3 +1,4 @@ +local baggage = require("opentelemetry.baggage") local otel_global = require("opentelemetry.global") local non_recording_span_new = require("opentelemetry.trace.non_recording_span").new local noop_span = require("opentelemetry.trace.noop_span") @@ -108,7 +109,7 @@ end -- @return baggage -------------------------------------------------------------------------------- function _M.extract_baggage(self) - return self:get(baggage_context_key) + return self:get(baggage_context_key) or baggage.new({}) end function _M.with_span(self, span) diff --git a/lib/opentelemetry/util.lua b/lib/opentelemetry/util.lua index af0b59a..79741c8 100644 --- a/lib/opentelemetry/util.lua +++ b/lib/opentelemetry/util.lua @@ -74,12 +74,48 @@ local function shallow_copy_table(t) return t2 end +local function hex_to_char(hex) + return string.char(tonumber(hex, 16)) +end + +local function char_to_hex(c) + return string.format("%%%02X", string.byte(c)) +end + +-- Baggage headers values can be percent encoded. We need to unescape them. The +-- regex is a bit weird-looking, so here's the relevant section on patterns in +-- the Lua manual (https://www.lua.org/manual/5.2/manual.html#6.4.1) +local function decode_percent_encoded_string(str) + return str:gsub("%%(%x%x)", hex_to_char) +end + +-------------------------------------------------------------------------------- +-- Percent encode a baggage string. It's not generic for all percent encoding, +-- since we don't want to percent-encode equals signs, semicolons, or commas in +-- baggage strings. +-- +-- @str string to be sent as baggage list item +-- @return new context with baggage associated +-------------------------------------------------------------------------------- +-- adapted from https://gist.github.com/liukun/f9ce7d6d14fa45fe9b924a3eed5c3d99 +local function percent_encode_baggage_string(str) + if str == nil then + return + end + str = str:gsub("\n", "\r\n") + str = str:gsub("([^%w ,;=_%%%-%.~])", char_to_hex) + str = str:gsub(" ", "+") + return str +end + _M.ngx_time_nano = ngx_time_nano _M.gettimeofday = ffi_gettimeofday _M.gettimeofday_ms = gettimeofday_ms _M.random = random _M.random_float = random_float _M.shallow_copy_table = shallow_copy_table +_M.decode_percent_encoded_string = decode_percent_encoded_string +_M.percent_encode_baggage_string = percent_encode_baggage_string -- default time function, will be used in this SDK -- change it if needed diff --git a/spec/baggage/propagation/text_map/baggage_propagator_spec.lua b/spec/baggage/propagation/text_map/baggage_propagator_spec.lua new file mode 100644 index 0000000..d10d7a0 --- /dev/null +++ b/spec/baggage/propagation/text_map/baggage_propagator_spec.lua @@ -0,0 +1,122 @@ +local baggage = require "opentelemetry.baggage" +local baggage_propagator = require "opentelemetry.baggage.propagation.text_map.baggage_propagator" +local tracer_provider = require "opentelemetry.trace.tracer_provider" +local context = require "opentelemetry.context" + +local function newCarrier(header, header_return) + local r = { headers = {} } + r.headers[header] = header_return + r.get_headers = function() return r.headers end + r.set_header = function(name, val) r.headers[name] = val end + return r +end + +-- todo(plantfansam): handle multiple baggage headers +describe("baggage propagator", function() + describe(".extract", function() + it("handles absent header", function() + local carrier = newCarrier("foo", "bar") + local ctx = context.new() + local baggage_propagator = baggage_propagator.new() + local new_ctx = baggage_propagator:extract(ctx, carrier) + local baggage_from_ctx = new_ctx:extract_baggage() + assert.is_same(baggage_from_ctx.values, {}) + end) + + it("handles empty string", function() + local carrier = newCarrier("baggage", "") + local ctx = context.new() + local baggage_propagator = baggage_propagator.new() + local new_ctx = baggage_propagator:extract(ctx, carrier) + local baggage_from_ctx = new_ctx:extract_baggage() + assert.is_same(baggage_from_ctx.values, {}) + end) + + it("handles simplest case", function() + local carrier = newCarrier("baggage", "userId=1") + local ctx = context.new() + local baggage_propagator = baggage_propagator.new() + local new_ctx = baggage_propagator:extract(ctx, carrier) + local baggage_from_ctx = new_ctx:extract_baggage() + assert.is_same(baggage_from_ctx:get_value("userId"), "1") + end) + + it("handles unescaping percent encoding", function() + local carrier = newCarrier("baggage", "userId=Am%C3%A9lie,serverNode=DF%2028,isProduction=false") + local ctx = context.new() + local baggage_propagator = baggage_propagator.new() + local new_ctx = baggage_propagator:extract(ctx, carrier) + local baggage_from_ctx = new_ctx:extract_baggage() + assert.is_same(baggage_from_ctx:get_value("userId"), "Amélie") + assert.is_same(baggage_from_ctx:get_value("serverNode"), "DF 28") + assert.is_same(baggage_from_ctx:get_value("isProduction"), "false") + end) + + it("extracts metadata", function() + local carrier = newCarrier("baggage", "userId=Am%C3%A9lie;motto=yolo;hi=mom,serverNode=DF%2028;motto=yolo2") + local ctx = context.new() + local baggage_propagator = baggage_propagator.new() + local new_ctx = baggage_propagator:extract(ctx, carrier) + local baggage_from_ctx = new_ctx:extract_baggage() + assert.is_same(baggage_from_ctx.values["userId"].metadata, "motto=yolo;hi=mom") + assert.is_same(baggage_from_ctx.values["serverNode"].metadata, "motto=yolo2") + end) + + it("handles malformed strings", function() + local carrier = newCarrier("baggage", "oi;=un;unx;p;=aun") + local ctx = context.new() + local baggage_propagator = baggage_propagator.new() + local new_ctx = baggage_propagator:extract(ctx, carrier) + local baggage_from_ctx = new_ctx:extract_baggage() + assert.is_same(baggage_from_ctx.values, {}) + end) + end) + + describe(".inject", function() + it("does nothing when baggage has no entries", function() + local carrier = newCarrier("foo", "bar") + local bgg = baggage.new({}) + local ctx = context.new():inject_baggage(bgg) + local baggage_propagator = baggage_propagator.new() + spy.on(carrier, "set_header") + baggage_propagator:extract(ctx, carrier) + assert.spy(carrier.set_header).was_not_called() + end) + + it("injects baggage header", function() + local carrier = newCarrier("foo", "bar") + local bgg = baggage.new({}) + bgg = bgg:set_value("userId", "Amélie", "mycoolmetadata;hi=mom") + local ctx = context.new() + ctx = ctx:inject_baggage(bgg) + local baggage_propagator = baggage_propagator.new() + baggage_propagator:inject(ctx, carrier) + assert.is_same("userId=Am%C3%A9lie;mycoolmetadata;hi=mom", carrier.get_headers()["baggage"]) + end) + + it("injects multiple values into header", function() + local carrier = newCarrier("foo", "bar") + local bgg = baggage.new({}) + bgg = bgg:set_value("foo", "bar") + bgg = bgg:set_value("userId", "Amélie", "mycoolmetadata;hi=mom") + local ctx = context.new() + ctx = ctx:inject_baggage(bgg) + local baggage_propagator = baggage_propagator.new() + baggage_propagator:inject(ctx, carrier) + + -- This is ugly. Tables have no intrinsic order in Lua, and I don't + -- want to add computational overhead to the :inject method to make + -- the test deterministic, so we need to check for both valid + -- headers. + local match = false + for k, v in pairs({ "userId=Am%C3%A9lie;mycoolmetadata;hi=mom,foo=bar", + "foo=bar,userId=Am%C3%A9lie;mycoolmetadata;hi=mom" }) do + if carrier.get_headers()["baggage"] == v then + match = true + break + end + end + assert.is_true(match) + end) + end) +end) diff --git a/spec/baggage_spec.lua b/spec/baggage_spec.lua index 31aaab9..9ef2150 100644 --- a/spec/baggage_spec.lua +++ b/spec/baggage_spec.lua @@ -15,9 +15,9 @@ describe("set_value and get_value", function() end) end) -describe("get_values", function() +describe("get__all_values", function() it("returns all values", function() - local values = { key1 = { value = "wat" }, key2 = { value = "wat2" } } + local values = { key1 = { value = "wat", metadata = "ignore" }, key2 = { value = "wat2", metadata } } local baggage = baggage.new(values) assert.are.same(baggage:get_all_values(), values) end) From b8c9068fc35afe3642b75bb69b180fb752c75288 Mon Sep 17 00:00:00 2001 From: Sam Handler Date: Fri, 12 Aug 2022 15:20:49 -0600 Subject: [PATCH 4/5] add new files to rockspec --- rockspec/opentelemetry-lua-master-0.rockspec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rockspec/opentelemetry-lua-master-0.rockspec b/rockspec/opentelemetry-lua-master-0.rockspec index 726bbae..e6b9aba 100644 --- a/rockspec/opentelemetry-lua-master-0.rockspec +++ b/rockspec/opentelemetry-lua-master-0.rockspec @@ -49,6 +49,8 @@ build = { ["opentelemetry.trace.span_status"] = "lib/opentelemetry/trace/span_status.lua", ["opentelemetry.trace.tracer"] = "lib/opentelemetry/trace/tracer.lua", ["opentelemetry.trace.tracer_provider"] = "lib/opentelemetry/trace/tracer_provider.lua", + ["opentelemetry.baggage"] = "lib/opentelemetry/baggage.lua", + ["opentelemetry.baggage.propagation.text_map.baggage_propagator"] = "lib/opentelemetry/baggage/propagation/text_map/baggage_propagator.lua", ["opentelemetry.util"] = "lib/opentelemetry/util.lua" } } From 1973ede126fdcb5b11323102ca901c5f0c5f5806 Mon Sep 17 00:00:00 2001 From: Sam Handler Date: Mon, 15 Aug 2022 13:42:27 -0600 Subject: [PATCH 5/5] wip --- Makefile | 2 +- busted-runner | 2 +- lib/opentelemetry/debugger.lua | 714 ++++++++++++++++++ .../text_map/composite_propagator_spec.lua | 28 +- 4 files changed, 736 insertions(+), 10 deletions(-) create mode 100644 lib/opentelemetry/debugger.lua diff --git a/Makefile b/Makefile index f706c71..7ee8b43 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ openresty-unit-test: $(CONTAINER_ORCHESTRATOR) exec $(CONTAINER_ORCHESTRATOR_EXEC_OPTIONS) -- openresty bash -c 'cd /opt/opentelemetry-lua && prove -r' lua-unit-test: - $(CONTAINER_ORCHESTRATOR) run $(CONTAINER_ORCHESTRATOR_EXEC_OPTIONS) -- openresty-test bash -c 'cd /opt/opentelemetry-lua && ./busted-runner' + $(CONTAINER_ORCHESTRATOR) run $(CONTAINER_ORCHESTRATOR_EXEC_OPTIONS) -- openresty-test bash -c 'cd /opt/opentelemetry-lua && ./busted-runner' openresty-build: $(CONTAINER_ORCHESTRATOR) build diff --git a/busted-runner b/busted-runner index 4301b79..940eeb7 100755 --- a/busted-runner +++ b/busted-runner @@ -3,7 +3,7 @@ package.path = './lib/?.lua;./lib/?/?.lua;./lib/?/init.lua' .. package.path _TEST = true -_RUN_SLOW_TESTS = os.getenv("RUN_SLOW_TESTS") or false +_RUN_SLOW_TESTS = true -- Set up global tracer Global = require("opentelemetry.global") diff --git a/lib/opentelemetry/debugger.lua b/lib/opentelemetry/debugger.lua new file mode 100644 index 0000000..3b8779c --- /dev/null +++ b/lib/opentelemetry/debugger.lua @@ -0,0 +1,714 @@ +--[[ + Copyright (c) 2020 Scott Lembcke and Howling Moon Software + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + TODO: + * Print short function arguments as part of stack location. + * Properly handle being reentrant due to coroutines. +]] + +local dbg + +-- Use ANSI color codes in the prompt by default. +local COLOR_GRAY = "" +local COLOR_RED = "" +local COLOR_BLUE = "" +local COLOR_YELLOW = "" +local COLOR_RESET = "" +local GREEN_CARET = " => " + +local function pretty(obj, max_depth) + if max_depth == nil then max_depth = dbg.pretty_depth end + + -- Returns true if a table has a __tostring metamethod. + local function coerceable(tbl) + local meta = getmetatable(tbl) + return (meta and meta.__tostring) + end + + local function recurse(obj, depth) + if type(obj) == "string" then + -- Dump the string so that escape sequences are printed. + return string.format("%q", obj) + elseif type(obj) == "table" and depth < max_depth and not coerceable(obj) then + local str = "{" + + for k, v in pairs(obj) do + local pair = pretty(k, 0) .. " = " .. recurse(v, depth + 1) + str = str .. (str == "{" and pair or ", " .. pair) + end + + return str .. "}" + else + -- tostring() can fail if there is an error in a __tostring metamethod. + local success, value = pcall(function() return tostring(obj) end) + return (success and value or "") + end + end + + return recurse(obj, 0) +end + +-- The stack level that cmd_* functions use to access locals or info +-- The structure of the code very carefully ensures this. +local CMD_STACK_LEVEL = 6 + +-- Location of the top of the stack outside of the debugger. +-- Adjusted by some debugger entrypoints. +local stack_top = 0 + +-- The current stack frame index. +-- Changed using the up/down commands +local stack_inspect_offset = 0 + +-- LuaJIT has an off by one bug when setting local variables. +local LUA_JIT_SETLOCAL_WORKAROUND = 0 + +-- Default dbg.read function +local function dbg_read(prompt) + dbg.write(prompt) + io.flush() + return io.read() +end + +-- Default dbg.write function +local function dbg_write(str) + io.write(str) +end + +local function dbg_writeln(str, ...) + if select("#", ...) == 0 then + dbg.write((str or "") .. "\n") + else + dbg.write(string.format(str .. "\n", ...)) + end +end + +local function format_loc(file, line) return COLOR_BLUE .. file .. COLOR_RESET .. ":" .. COLOR_YELLOW .. + line .. COLOR_RESET end + +local function format_stack_frame_info(info) + local filename = info.source:match("@(.*)") + local source = filename and dbg.shorten_path(filename) or info.short_src + local namewhat = (info.namewhat == "" and "chunk at" or info.namewhat) + local name = (info.name and "'" .. COLOR_BLUE .. info.name .. COLOR_RESET .. "'" or + format_loc(source, info.linedefined)) + return format_loc(source, info.currentline) .. " in " .. namewhat .. " " .. name +end + +local repl + +-- Return false for stack frames without source, +-- which includes C frames, Lua bytecode, and `loadstring` functions +local function frame_has_line(info) return info.currentline >= 0 end + +local function hook_factory(repl_threshold) + return function(offset, reason) + return function(event, _) + -- Skip events that don't have line information. + if not frame_has_line(debug.getinfo(2)) then return end + + -- Tail calls are specifically ignored since they also will have tail returns to balance out. + if event == "call" then + offset = offset + 1 + elseif event == "return" and offset > repl_threshold then + offset = offset - 1 + elseif event == "line" and offset <= repl_threshold then + repl(reason) + end + end + end +end + +local hook_step = hook_factory(1) +local hook_next = hook_factory(0) +local hook_finish = hook_factory(-1) + +-- Create a table of all the locally accessible variables. +-- Globals are not included when running the locals command, but are when running the print command. +local function local_bindings(offset, include_globals) + local level = offset + stack_inspect_offset + CMD_STACK_LEVEL + local func = debug.getinfo(level).func + local bindings = {} + + -- Retrieve the upvalues + do local i = 1; + while true do + local name, value = debug.getupvalue(func, i) + if not name then break end + bindings[name] = value + i = i + 1 + end + end + + -- Retrieve the locals (overwriting any upvalues) + do local i = 1; + while true do + local name, value = debug.getlocal(level, i) + if not name then break end + bindings[name] = value + i = i + 1 + end + end + + -- Retrieve the varargs (works in Lua 5.2 and LuaJIT) + local varargs = {} + do local i = 1; + while true do + local name, value = debug.getlocal(level, -i) + if not name then break end + varargs[i] = value + i = i + 1 + end + end + if #varargs > 0 then bindings["..."] = varargs end + + if include_globals then + -- In Lua 5.2, you have to get the environment table from the function's locals. + local env = (_VERSION <= "Lua 5.1" and getfenv(func) or bindings._ENV) + return setmetatable(bindings, { __index = env or _G }) + else + return bindings + end +end + +-- Used as a __newindex metamethod to modify variables in cmd_eval(). +local function mutate_bindings(_, name, value) + local FUNC_STACK_OFFSET = 3 -- Stack depth of this function. + local level = stack_inspect_offset + FUNC_STACK_OFFSET + CMD_STACK_LEVEL + + -- Set a local. + do local i = 1; + repeat + local var = debug.getlocal(level, i) + if name == var then + dbg_writeln(COLOR_YELLOW .. + "debugger.lua" .. GREEN_CARET .. "Set local variable " .. COLOR_BLUE .. name .. COLOR_RESET) + return debug.setlocal(level + LUA_JIT_SETLOCAL_WORKAROUND, i, value) + end + i = i + 1 + until var == nil + end + + -- Set an upvalue. + local func = debug.getinfo(level).func + do local i = 1; + repeat + local var = debug.getupvalue(func, i) + if name == var then + dbg_writeln(COLOR_YELLOW .. "debugger.lua" .. GREEN_CARET .. + "Set upvalue " .. COLOR_BLUE .. name .. COLOR_RESET) + return debug.setupvalue(func, i, value) + end + i = i + 1 + until var == nil + end + + -- Set a global. + dbg_writeln(COLOR_YELLOW .. "debugger.lua" .. GREEN_CARET .. "Set global variable " .. COLOR_BLUE .. + name .. COLOR_RESET) + _G[name] = value +end + +-- Compile an expression with the given variable bindings. +local function compile_chunk(block, env) + local source = "debugger.lua REPL" + local chunk = nil + + if _VERSION <= "Lua 5.1" then + chunk = loadstring(block, source) + if chunk then setfenv(chunk, env) end + else + -- The Lua 5.2 way is a bit cleaner + chunk = load(block, source, "t", env) + end + + if not chunk then dbg_writeln(COLOR_RED .. "Error: Could not compile block:\n" .. COLOR_RESET .. block) end + return chunk +end + +local SOURCE_CACHE = {} + +local function where(info, context_lines) + local source = SOURCE_CACHE[info.source] + if not source then + source = {} + local filename = info.source:match("@(.*)") + if filename then + pcall(function() for line in io.lines(filename) do table.insert(source, line) end end) + elseif info.source then + for line in info.source:gmatch("(.-)\n") do table.insert(source, line) end + end + SOURCE_CACHE[info.source] = source + end + + if source and source[info.currentline] then + for i = info.currentline - context_lines, info.currentline + context_lines do + local tab_or_caret = (i == info.currentline and GREEN_CARET or " ") + local line = source[i] + if line then dbg_writeln(COLOR_GRAY .. "% 4d" .. tab_or_caret .. "%s", i, line) end + end + else + dbg_writeln(COLOR_RED .. "Error: Source not available for " .. COLOR_BLUE .. info.short_src); + end + + return false +end + +-- Wee version differences +local unpack = unpack or table.unpack +local pack = function(...) return { n = select("#", ...), ... } end + +local function cmd_step() + stack_inspect_offset = stack_top + return true, hook_step +end + +local function cmd_next() + stack_inspect_offset = stack_top + return true, hook_next +end + +local function cmd_finish() + local offset = stack_top - stack_inspect_offset + stack_inspect_offset = stack_top + return true, offset < 0 and hook_factory(offset - 1) or hook_finish +end + +local function cmd_print(expr) + local env = local_bindings(1, true) + local chunk = compile_chunk("return " .. expr, env) + if chunk == nil then return false end + + -- Call the chunk and collect the results. + local results = pack(pcall(chunk, unpack(rawget(env, "...") or {}))) + + -- The first result is the pcall error. + if not results[1] then + dbg_writeln(COLOR_RED .. "Error:" .. COLOR_RESET .. " " .. results[2]) + else + local output = "" + for i = 2, results.n do + output = output .. (i ~= 2 and ", " or "") .. pretty(results[i]) + end + + if output == "" then output = "" end + dbg_writeln(COLOR_BLUE .. expr .. GREEN_CARET .. output) + end + + return false +end + +local function cmd_eval(code) + local env = local_bindings(1, true) + local mutable_env = setmetatable({}, { + __index = env, + __newindex = mutate_bindings, + }) + + local chunk = compile_chunk(code, mutable_env) + if chunk == nil then return false end + + -- Call the chunk and collect the results. + local success, err = pcall(chunk, unpack(rawget(env, "...") or {})) + if not success then + dbg_writeln(COLOR_RED .. "Error:" .. COLOR_RESET .. " " .. tostring(err)) + end + + return false +end + +local function cmd_down() + local offset = stack_inspect_offset + local info + + repeat -- Find the next frame with a file. + offset = offset + 1 + info = debug.getinfo(offset + CMD_STACK_LEVEL) + until not info or frame_has_line(info) + + if info then + stack_inspect_offset = offset + dbg_writeln("Inspecting frame: " .. format_stack_frame_info(info)) + if tonumber(dbg.auto_where) then where(info, dbg.auto_where) end + else + info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL) + dbg_writeln("Already at the bottom of the stack.") + end + + return false +end + +local function cmd_up() + local offset = stack_inspect_offset + local info + + repeat -- Find the next frame with a file. + offset = offset - 1 + if offset < stack_top then info = nil; break end + info = debug.getinfo(offset + CMD_STACK_LEVEL) + until frame_has_line(info) + + if info then + stack_inspect_offset = offset + dbg_writeln("Inspecting frame: " .. format_stack_frame_info(info)) + if tonumber(dbg.auto_where) then where(info, dbg.auto_where) end + else + info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL) + dbg_writeln("Already at the top of the stack.") + end + + return false +end + +local function cmd_where(context_lines) + local info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL) + return (info and where(info, tonumber(context_lines) or 5)) +end + +local function cmd_trace() + dbg_writeln("Inspecting frame %d", stack_inspect_offset - stack_top) + local i = 0; + while true do + local info = debug.getinfo(stack_top + CMD_STACK_LEVEL + i) + if not info then break end + + local is_current_frame = (i + stack_top == stack_inspect_offset) + local tab_or_caret = (is_current_frame and GREEN_CARET or " ") + dbg_writeln(COLOR_GRAY .. "% 4d" .. COLOR_RESET .. tab_or_caret .. "%s", i, format_stack_frame_info(info)) + i = i + 1 + end + + return false +end + +local function cmd_locals() + local bindings = local_bindings(1, false) + + -- Get all the variable binding names and sort them + local keys = {} + for k, _ in pairs(bindings) do table.insert(keys, k) end + table.sort(keys) + + for _, k in ipairs(keys) do + local v = bindings[k] + + -- Skip the debugger object itself, "(*internal)" values, and Lua 5.2's _ENV object. + if not rawequal(v, dbg) and k ~= "_ENV" and not k:match("%(.*%)") then + dbg_writeln(" " .. COLOR_BLUE .. k .. GREEN_CARET .. pretty(v)) + end + end + + return false +end + +local function cmd_help() + dbg.write("" + .. COLOR_BLUE .. " " .. GREEN_CARET .. "re-run last command\n" + .. COLOR_BLUE .. " c" .. COLOR_YELLOW .. "(ontinue)" .. GREEN_CARET .. "continue execution\n" + .. COLOR_BLUE .. " s" .. COLOR_YELLOW .. "(tep)" .. GREEN_CARET .. + "step forward by one line (into functions)\n" + .. + COLOR_BLUE .. " n" .. + COLOR_YELLOW .. "(ext)" .. GREEN_CARET .. "step forward by one line (skipping over functions)\n" + .. COLOR_BLUE .. + " f" .. COLOR_YELLOW .. "(inish)" .. GREEN_CARET .. "step forward until exiting the current function\n" + .. COLOR_BLUE .. " u" .. COLOR_YELLOW .. "(p)" .. GREEN_CARET .. "move up the stack by one frame\n" + .. COLOR_BLUE .. " d" .. COLOR_YELLOW .. "(own)" .. GREEN_CARET .. "move down the stack by one frame\n" + .. + COLOR_BLUE .. + " w" .. + COLOR_YELLOW .. + "(here) " .. COLOR_BLUE .. "[line count]" .. GREEN_CARET .. "print source code around the current line\n" + .. COLOR_BLUE .. + " e" .. COLOR_YELLOW .. "(val) " .. COLOR_BLUE .. "[statement]" .. GREEN_CARET .. "execute the statement\n" + .. + COLOR_BLUE .. + " p" .. + COLOR_YELLOW .. + "(rint) " .. COLOR_BLUE .. "[expression]" .. GREEN_CARET .. "execute the expression and print the result\n" + .. COLOR_BLUE .. " t" .. COLOR_YELLOW .. "(race)" .. GREEN_CARET .. "print the stack trace\n" + .. + COLOR_BLUE .. + " l" .. COLOR_YELLOW .. "(ocals)" .. GREEN_CARET .. "print the function arguments, locals and upvalues.\n" + .. COLOR_BLUE .. " h" .. COLOR_YELLOW .. "(elp)" .. GREEN_CARET .. "print this message\n" + .. COLOR_BLUE .. " q" .. COLOR_YELLOW .. "(uit)" .. GREEN_CARET .. "halt execution\n" + ) + return false +end + +local last_cmd = false + +local commands = { + ["^c$"] = function() return true end, + ["^s$"] = cmd_step, + ["^n$"] = cmd_next, + ["^f$"] = cmd_finish, + ["^p%s+(.*)$"] = cmd_print, + ["^e%s+(.*)$"] = cmd_eval, + ["^u$"] = cmd_up, + ["^d$"] = cmd_down, + ["^w%s*(%d*)$"] = cmd_where, + ["^t$"] = cmd_trace, + ["^l$"] = cmd_locals, + ["^h$"] = cmd_help, + ["^q$"] = function() dbg.exit(0); return true end, +} + +local function match_command(line) + for pat, func in pairs(commands) do + -- Return the matching command and capture argument. + if line:find(pat) then return func, line:match(pat) end + end +end + +-- Run a command line +-- Returns true if the REPL should exit and the hook function factory +local function run_command(line) + -- GDB/LLDB exit on ctrl-d + if line == nil then dbg.exit(1); return true end + + -- Re-execute the last command if you press return. + if line == "" then line = last_cmd or "h" end + + local command, command_arg = match_command(line) + if command then + last_cmd = line + -- unpack({...}) prevents tail call elimination so the stack frame indices are predictable. + return unpack({ command(command_arg) }) + elseif dbg.auto_eval then + return unpack({ cmd_eval(line) }) + else + dbg_writeln(COLOR_RED .. + "Error:" .. COLOR_RESET .. " command '%s' not recognized.\nType 'h' and press return for a command list.", + line) + return false + end +end + +repl = function(reason) + -- Skip frames without source info. + while not frame_has_line(debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL - 3)) do + stack_inspect_offset = stack_inspect_offset + 1 + end + + local info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL - 3) + reason = reason and (COLOR_YELLOW .. "break via " .. COLOR_RED .. reason .. GREEN_CARET) or "" + dbg_writeln(reason .. format_stack_frame_info(info)) + + if tonumber(dbg.auto_where) then where(info, dbg.auto_where) end + + repeat + local success, done, hook = pcall(run_command, dbg.read(COLOR_RED .. "debugger.lua> " .. COLOR_RESET)) + if success then + debug.sethook(hook and hook(0), "crl") + else + local message = COLOR_RED .. "INTERNAL DEBUGGER.LUA ERROR. ABORTING\n:" .. COLOR_RESET .. " " .. done + dbg_writeln(message) + error(message) + end + until done +end + +-- Make the debugger object callable like a function. +dbg = setmetatable({}, { + __call = function(_, condition, top_offset, source) + if condition then return end + + top_offset = (top_offset or 0) + stack_inspect_offset = top_offset + stack_top = top_offset + + debug.sethook(hook_next(1, source or "dbg()"), "crl") + return + end, +}) + +-- Expose the debugger's IO functions. +dbg.read = dbg_read +dbg.write = dbg_write +dbg.shorten_path = function(path) return path end +dbg.exit = function(err) os.exit(err) end + +dbg.writeln = dbg_writeln + +dbg.pretty_depth = 3 +dbg.pretty = pretty +dbg.pp = function(value, depth) dbg_writeln(pretty(value, depth)) end + +dbg.auto_where = false +dbg.auto_eval = false + +local lua_error, lua_assert = error, assert + +-- Works like error(), but invokes the debugger. +function dbg.error(err, level) + level = level or 1 + dbg_writeln(COLOR_RED .. "ERROR: " .. COLOR_RESET .. pretty(err)) + dbg(false, level, "dbg.error()") + + lua_error(err, level) +end + +-- Works like assert(), but invokes the debugger on a failure. +function dbg.assert(condition, message) + if not condition then + dbg_writeln(COLOR_RED .. "ERROR:" .. COLOR_RESET .. message) + dbg(false, 1, "dbg.assert()") + end + + return lua_assert(condition, message) +end + +-- Works like pcall(), but invokes the debugger on an error. +function dbg.call(f, ...) + return xpcall(f, function(err) + dbg_writeln(COLOR_RED .. "ERROR: " .. COLOR_RESET .. pretty(err)) + dbg(false, 1, "dbg.call()") + + return err + end, ...) +end + +-- Error message handler that can be used with lua_pcall(). +function dbg.msgh(...) + if debug.getinfo(2) then + dbg_writeln(COLOR_RED .. "ERROR: " .. COLOR_RESET .. pretty(...)) + dbg(false, 1, "dbg.msgh()") + else + dbg_writeln(COLOR_RED .. + "debugger.lua: " .. + COLOR_RESET .. "Error did not occur in Lua code. Execution will continue after dbg_pcall().") + end + + return ... +end + +-- Assume stdin/out are TTYs unless we can use LuaJIT's FFI to properly check them. +local stdin_isatty = true +local stdout_isatty = true + +-- Conditionally enable the LuaJIT FFI. +local ffi = (jit and require("ffi")) +if ffi then + ffi.cdef [[ + int isatty(int); // Unix + int _isatty(int); // Windows + void free(void *ptr); + + char *readline(const char *); + int add_history(const char *); + ]] + + local function get_func_or_nil(sym) + local success, func = pcall(function() return ffi.C[sym] end) + return success and func or nil + end + + local isatty = get_func_or_nil("isatty") or get_func_or_nil("_isatty") or (ffi.load("ucrtbase"))["_isatty"] + stdin_isatty = isatty(0) + stdout_isatty = isatty(1) +end + +-- Conditionally enable color support. +local color_maybe_supported = (stdout_isatty and os.getenv("TERM") and os.getenv("TERM") ~= "dumb") +if color_maybe_supported and not os.getenv("DBG_NOCOLOR") then + COLOR_GRAY = string.char(27) .. "[90m" + COLOR_RED = string.char(27) .. "[91m" + COLOR_BLUE = string.char(27) .. "[94m" + COLOR_YELLOW = string.char(27) .. "[33m" + COLOR_RESET = string.char(27) .. "[0m" + GREEN_CARET = string.char(27) .. "[92m => " .. COLOR_RESET +end + +if stdin_isatty and not os.getenv("DBG_NOREADLINE") then + pcall(function() + local linenoise = require 'linenoise' + + -- Load command history from ~/.lua_history + local hist_path = os.getenv('HOME') .. '/.lua_history' + linenoise.historyload(hist_path) + linenoise.historysetmaxlen(50) + + local function autocomplete(env, input, matches) + for name, _ in pairs(env) do + if name:match('^' .. input .. '.*') then + linenoise.addcompletion(matches, name) + end + end + end + + -- Auto-completion for locals and globals + linenoise.setcompletion(function(matches, input) + -- First, check the locals and upvalues. + local env = local_bindings(1, true) + autocomplete(env, input, matches) + + -- Then, check the implicit environment. + env = getmetatable(env).__index + autocomplete(env, input, matches) + end) + + dbg.read = function(prompt) + local str = linenoise.linenoise(prompt) + if str and not str:match "^%s*$" then + linenoise.historyadd(str) + linenoise.historysave(hist_path) + end + return str + end + dbg_writeln(COLOR_YELLOW .. "debugger.lua: " .. COLOR_RESET .. "Linenoise support enabled.") + end) + + -- Conditionally enable LuaJIT readline support. + pcall(function() + if dbg.read == nil and ffi then + local readline = ffi.load("readline") + dbg.read = function(prompt) + local cstr = readline.readline(prompt) + if cstr ~= nil then + local str = ffi.string(cstr) + if string.match(str, "[^%s]+") then + readline.add_history(cstr) + end + + ffi.C.free(cstr) + return str + else + return nil + end + end + dbg_writeln(COLOR_YELLOW .. "debugger.lua: " .. COLOR_RESET .. "Readline support enabled.") + end + end) +end + +-- Detect Lua version. +if jit then -- LuaJIT + LUA_JIT_SETLOCAL_WORKAROUND = -1 + dbg_writeln(COLOR_YELLOW .. "debugger.lua: " .. COLOR_RESET .. "Loaded for " .. jit.version) +elseif "Lua 5.1" <= _VERSION and _VERSION <= "Lua 5.4" then + dbg_writeln(COLOR_YELLOW .. "debugger.lua: " .. COLOR_RESET .. "Loaded for " .. _VERSION) +else + dbg_writeln(COLOR_YELLOW .. "debugger.lua: " .. COLOR_RESET .. "Not tested against " .. _VERSION) + dbg_writeln("Please send me feedback!") +end + +return dbg diff --git a/spec/trace/propagation/text_map/composite_propagator_spec.lua b/spec/trace/propagation/text_map/composite_propagator_spec.lua index d539f03..5734105 100644 --- a/spec/trace/propagation/text_map/composite_propagator_spec.lua +++ b/spec/trace/propagation/text_map/composite_propagator_spec.lua @@ -1,14 +1,16 @@ +local baggage = require "opentelemetry.baggage" local tracer_provider = require "opentelemetry.trace.tracer_provider" local composite_propagator = require "opentelemetry.trace.propagation.text_map.composite_propagator" local text_map_propagator = require "opentelemetry.trace.propagation.text_map.trace_context_propagator" +local baggage_propagator = require "opentelemetry.baggage.propagation.text_map.baggage_propagator" local noop_propagator = require "opentelemetry.trace.propagation.text_map.noop_propagator" local context = require("opentelemetry.context") -- We're setting these on ngx.req but we aren't running openresty in these -- tests, so we'll mock that out (ngx.req supports get_headers() and set_header(header_name)) -local function newCarrier(header, header_return) +local function newCarrier(headers_table) local r = { headers = {} } - r.headers[header] = header_return + r.headers = headers_table r.get_headers = function() return r.headers end r.set_header = function(name, val) r.headers[name] = val end return r @@ -19,14 +21,17 @@ end describe("composite propagator", function() describe(".inject", function() local tmp = text_map_propagator.new() + local bp = baggage_propagator.new() local np = noop_propagator.new() - local cp = composite_propagator.new({ tmp, np }) + local cp = composite_propagator.new({ tmp, bp, np }) local ctx = context.new() local tracer_provider = tracer_provider.new() local tracer = tracer_provider:tracer("test tracer") -- start a trace local new_ctx = tracer:start(ctx, "span", { kind = 1 }) + local bgg = baggage.new({ foo = { value = "bar", metadata = "ok" } }) + new_ctx = new_ctx:inject_baggage(bgg) -- figure out what traceheader should look like local span_context = new_ctx.sp:context() @@ -34,7 +39,7 @@ describe("composite propagator", function() span_context.trace_id, span_context.span_id, span_context.trace_flags) - local carrier = newCarrier("header", "value") + local carrier = newCarrier({}) it("should add headers for each propagator", function() cp:inject(new_ctx, carrier) @@ -42,20 +47,27 @@ describe("composite propagator", function() carrier.get_headers()["traceparent"], traceparent ) + assert.are.same( + carrier.get_headers()["baggage"], + "foo=bar;ok" + ) end) end) - describe(".extract #focus", function() + describe(".extract", function() it("should extract headers for each propagator", function() local tmp = text_map_propagator.new() + local bp = baggage_propagator.new() local np = noop_propagator.new() - local cp = composite_propagator.new({ tmp, np }) + local cp = composite_propagator.new({ tmp, bp, np }) local trace_id = "10f5b3bcfe3f0c2c5e1ef150fe0b5872" - local carrier = newCarrier("traceparent", - string.format("00-%s-172accbce5f048db-01", trace_id)) + local carrier = newCarrier( + { traceparent = string.format("00-%s-172accbce5f048db-01", trace_id), + baggage = "foo=bar;ok" }) local ctx = context.new() local new_ctx = cp:extract(ctx, carrier) assert.are.same(new_ctx.sp:context().trace_id, trace_id) + assert.are.same(new_ctx:extract_baggage():get_value("foo"), "bar") end) end) end)