Skip to content

Commit a81fd3e

Browse files
aliibiialibakhshoude-afk
authored andcommitted
feat: introduced with_span() in otel
1 parent 8af172f commit a81fd3e

File tree

1 file changed

+103
-60
lines changed

1 file changed

+103
-60
lines changed

apisix/plugins/opentelemetry.lua

Lines changed: 103 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,11 @@ end
345345
-- Plugin span management functions
346346
-- =================================
347347

348+
-- Build a consistent key for identifying a plugin phase span
349+
local function build_plugin_phase_key(plugin_name, phase)
350+
return plugin_name .. ":" .. phase
351+
end
352+
348353
-- Create phase span
349354
local function create_phase_span(api_ctx, plugin_name, phase)
350355
if not api_ctx.otel then
@@ -356,7 +361,7 @@ local function create_phase_span(api_ctx, plugin_name, phase)
356361
end
357362

358363
-- Create unique key for plugin+phase combination
359-
local span_key = plugin_name .. ":" .. phase
364+
local span_key = build_plugin_phase_key(plugin_name, phase)
360365
if not api_ctx.otel_plugin_spans[span_key] then
361366
-- Create span named "plugin_name phase" directly under main request span
362367
local phase_span_ctx = api_ctx.otel.start_span({
@@ -382,7 +387,7 @@ local function finish_phase_span(api_ctx, plugin_name, phase, error_msg)
382387
return
383388
end
384389

385-
local span_key = plugin_name .. ":" .. phase
390+
local span_key = build_plugin_phase_key(plugin_name, phase)
386391
local phase_span_ctx = api_ctx.otel_plugin_spans[span_key]
387392

388393
if phase_span_ctx then
@@ -404,7 +409,7 @@ local function cleanup_plugin_spans(api_ctx)
404409

405410
for span_key, phase_span_ctx in pairs(api_ctx.otel_plugin_spans) do
406411
if phase_span_ctx then
407-
api_ctx.otel.stop_span(phase_span_ctx, "plugin span cleanup: check logs for details")
412+
api_ctx.otel.stop_span(phase_span_ctx)
408413
end
409414
end
410415

@@ -417,23 +422,21 @@ end
417422
-- =============================
418423

419424
-- No-op API when tracing is disabled
420-
local noop_api = {
421-
start_span = function(span_info)
422-
return nil
423-
end,
424-
425-
stop_span = function(span_ctx, error_msg)
426-
-- no-op
427-
end,
428-
429-
current_span = function()
430-
return nil
431-
end,
432-
433-
get_plugin_context = function(plugin_name)
434-
return nil
425+
local noop_api = setmetatable({
426+
with_span = function(span_info, fn)
427+
if not fn then
428+
return nil, "with_span: function is required"
429+
end
430+
-- Execute function without tracing
431+
local result = {pcall(fn)}
432+
-- Return unpacked results (starting from index 2 to preserve error-first pattern)
433+
return unpack(result, 2)
435434
end
436-
}
435+
}, {
436+
__index = function(_, _)
437+
return function() return nil end
438+
end
439+
})
437440

438441
-- Create simple OpenTelemetry API for plugins
439442
local function create_otel_api(api_ctx, tracer, main_context)
@@ -442,56 +445,60 @@ local function create_otel_api(api_ctx, tracer, main_context)
442445
api_ctx._otel_span_stack = {}
443446
end
444447

445-
return {
446-
start_span = function(span_info)
447-
if not (span_info and span_info.name) then
448-
return nil
449-
end
448+
local function start_span_func(span_info)
449+
if not (span_info and span_info.name) then
450+
return nil
451+
end
450452

451-
-- Get parent context (prioritize explicit parent, then current phase span, then main)
452-
local current_phase_span = api_ctx._current_plugin_phase and
453-
api_ctx.otel_plugin_spans and
454-
api_ctx.otel_plugin_spans[api_ctx._current_plugin_phase]
453+
-- Get parent context (prioritize explicit parent, then current phase span, then main)
454+
local current_phase_span = api_ctx._current_plugin_phase and
455+
api_ctx.otel_plugin_spans and
456+
api_ctx.otel_plugin_spans[api_ctx._current_plugin_phase]
455457

456-
local parent_context = span_info.parent or current_phase_span or main_context
458+
local parent_context = span_info.parent or current_phase_span or main_context
457459

458-
-- Use the provided kind directly (users should pass span_kind constants)
459-
local span_kind_value = span_info.kind or span_kind.internal
460-
local attributes = span_info.attributes or {}
461-
local span_ctx = tracer:start(parent_context, span_info.name, {
462-
kind = span_kind_value,
463-
attributes = attributes,
464-
})
460+
-- Use the provided kind directly (users should pass span_kind constants)
461+
local span_kind_value = span_info.kind or span_kind.internal
462+
local attributes = span_info.attributes or {}
463+
local span_ctx = tracer:start(parent_context, span_info.name, {
464+
kind = span_kind_value,
465+
attributes = attributes,
466+
})
465467

466-
-- Track this span as current (push to stack)
467-
core.table.insert(api_ctx._otel_span_stack, span_ctx)
468+
-- Track this span as current (push to stack)
469+
core.table.insert(api_ctx._otel_span_stack, span_ctx)
468470

469-
return span_ctx
470-
end,
471+
return span_ctx
472+
end
471473

472-
stop_span = function(span_ctx, error_msg)
473-
if not span_ctx then
474-
return
475-
end
474+
local function stop_span_func(span_ctx, error_msg)
475+
if not span_ctx then
476+
return
477+
end
476478

477-
local span = span_ctx:span()
478-
if not span then
479-
return
480-
end
479+
local span = span_ctx:span()
480+
if not span then
481+
return
482+
end
481483

482-
if error_msg then
483-
span:set_status(span_status.ERROR, error_msg)
484-
end
484+
if error_msg then
485+
span:set_status(span_status.ERROR, error_msg)
486+
end
485487

486-
span:finish()
488+
span:finish()
487489

488-
-- Remove from stack if it's the current span (pop from stack)
489-
if api_ctx._otel_span_stack and
490-
#api_ctx._otel_span_stack > 0 and
491-
api_ctx._otel_span_stack[#api_ctx._otel_span_stack] == span_ctx then
492-
core.table.remove(api_ctx._otel_span_stack)
493-
end
494-
end,
490+
-- Remove from stack if it's the current span (pop from stack)
491+
if api_ctx._otel_span_stack and
492+
#api_ctx._otel_span_stack > 0 and
493+
api_ctx._otel_span_stack[#api_ctx._otel_span_stack] == span_ctx then
494+
core.table.remove(api_ctx._otel_span_stack)
495+
end
496+
end
497+
498+
return {
499+
start_span = start_span_func,
500+
501+
stop_span = stop_span_func,
495502

496503
current_span = function()
497504
-- Return the most recently started span (top of stack)
@@ -505,7 +512,43 @@ local function create_otel_api(api_ctx, tracer, main_context)
505512
if not (api_ctx.otel_plugin_spans and phase) then
506513
return nil
507514
end
508-
return api_ctx.otel_plugin_spans[plugin_name .. ":" .. phase]
515+
return api_ctx.otel_plugin_spans[build_plugin_phase_key(plugin_name, phase)]
516+
end,
517+
518+
with_span = function(span_info, fn)
519+
if not fn then
520+
return nil, "with_span: function is required"
521+
end
522+
-- Start the span
523+
local span_ctx = start_span_func(span_info)
524+
if not span_ctx then
525+
-- If span creation fails, still execute the function without tracing
526+
local result = {pcall(fn)}
527+
return unpack(result, 2)
528+
end
529+
-- Execute function with pcall for error protection
530+
local result = {pcall(fn)}
531+
-- Handle results:
532+
-- - If pcall fails: result[1] = false, result[2] = Lua error
533+
-- - If function succeeds: result[1] = true, result[2] = err (from function), result[3+] = other values
534+
local pcall_success, error_msg = result[1], result[2]
535+
-- Determine the actual error to report:
536+
-- - If pcall failed, use the Lua error
537+
-- - If pcall succeeded but function returned an error, use the function error
538+
-- - Otherwise, no error
539+
local final_error = nil
540+
if not pcall_success then
541+
-- pcall failed - Lua error occurred
542+
final_error = error_msg
543+
elseif error_msg ~= nil then
544+
-- pcall succeeded but function returned an error
545+
final_error = error_msg
546+
end
547+
-- Stop span with error message if there was an error
548+
stop_span_func(span_ctx, final_error)
549+
-- Return unpacked results (starting from index 2 to preserve error-first pattern)
550+
-- This returns: err, ...values
551+
return unpack(result, 2)
509552
end
510553
}
511554
end

0 commit comments

Comments
 (0)