Skip to content

yangxikun/opentelemetry-lua

Folders and files

NameName
Last commit message
Last commit date
Feb 7, 2023
Apr 13, 2023
Jan 18, 2023
Sep 10, 2023
Jul 13, 2024
Jul 13, 2024
Mar 26, 2024
Mar 26, 2024
Feb 11, 2023
Jun 27, 2022
Jan 18, 2023
Jan 18, 2023
Apr 27, 2024
Feb 8, 2023
Jul 13, 2024
Dec 18, 2021
Apr 13, 2023
Sep 10, 2023
Feb 7, 2023
Apr 14, 2024
Jun 27, 2022
Apr 14, 2024

Repository files navigation

opentelemetry-lua

OpenTelemetry-Lua is the lua implementation of OpenTelemetry. It provides a set of APIs to directly measure performance and behavior of your software and send this data to observability platforms.

Project Status

This project currently lives in a alpha status.

INSTALL

Use luarocks

git clone this project, then run luarocks make in project root directory.

APIs

This lib is designed for Nginx+LUA/OpenResty ecosystems.

util

Set the nano time func used by this lib to set span start/end time.

local util = require("opentelemetry.util")
-- default
util.time_nano = util.gettimeofday
-- ngx.now
util.time_nano = util.time_nano

global

Set/Get the global tracer provider.

local global = require("opentelemetry.global")
-- tp is a tracer provider instance
global.set_tracer_provider(tp)
local tp2 = global.get_tracer_provider()
local tracer = global.tracer(name, opts)

Context

Partially implement of specification: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/context.md.

Create a Context

-- context is stored on ngx.ctx by default, so context is passed between different request phases.
local context = require("opentelemetry.context").new()

Attach/Detach Context

-- Associates a Context with the caller's current execution unit, so you can use context.current() to retrieve it. Returns a token you can use to detach.
local token = context:attach()
-- Resets the Context associated with the caller's current execution unit to the value it had before attaching a specified Context. Pass the token returned when attaching context.
context:detach(token)

Get current Context

local cur_context = context.current()

Get current span/span_context

local cur_span = context:span()
local cur_span_context = context:span_context()

Attribute

local attr = require("opentelemetry.attribute")
-- string attribute
attr.string("service.name", "openresty")
attr.string_array("service.name", {"openresty"})
-- int attribute
attr.int("attr_int", 100)
attr.int_array("attr_ints", {100, 1000})
-- double attribute
attr.double("attr_double", 10.24)
attr.double_array("attr_doubles", {10.24, 20.48})
-- bool attribute
attr.bool("attr_bool", true)
attr.bool_array("attr_bools", {true, false})

Resource

Partially implement of specification: https://github.com/open-telemetry/opentelemetry-specification/blob/01e62674c0ac23076736459efd0c05316f84cd6f/specification/resource/sdk.md

Create a resource

local attr = require("opentelemetry.attribute")
local resource_new = require("opentelemetry.resource").new

local resource = resource_new(attr.string("service.name", "openresty"), attr.int("attr_int", 100),
        attr.double("attr_double", 10.24), attr.bool("attr_bool", true))

Get attributes

resource:attributes()

Trace

Partially implement of specification:

TracerProvider

local attr = require("opentelemetry.attribute")
local resource_new = require("opentelemetry.resource").new
local always_on_sampler = require("opentelemetry.trace.sampling.always_on_sampler").new()
local tp_new = require("opentelemetry.trace.tracer_provider").new
------------------------------------------------------------------
-- create a tracer provider.
--
-- @span_processor      [optional] span_processor
-- @opts                [optional] config
--                          opts.sampler: opentelemetry.trace.sampling.*, default parent_base_sampler
--                          opts.resource
-- @return              tracer provider factory
------------------------------------------------------------------
local tp = tp_new(span_processor, {
    sampler = always_on_sampler,
    resource = resource_new(attr.string("service.name", "openresty"), attr.int("attr_int", 100)),
})

------------------------------------------------------------------
-- create a tracer.
--
-- @name            tracer name
-- @opts            [optional] config
--                      opts.version: specifies the version of the instrumentation library
--                      opts.schema_url: specifies the Schema URL that should be recorded in the emitted telemetry.
-- @return          tracer
------------------------------------------------------------------
local tracer = tp:tracer("opentelemetry-lua", opts)

-- force all processors export spans
tp:force_flush()

-- force all processors export spans and shutdown processors
tp:shutdown()

-- append processor
tp:register_span_processor(span_processor)

-- set processors
tp:set_span_processors(span_processor1, span_processor2)

Span Processor

opentelemetry.trace.batch_span_processor will store spans in a queue, and start a background timer to export spans.

local batch_span_processor_new = require("opentelemetry.trace.batch_span_processor").new

------------------------------------------------------------------
-- create a batch span processor.
--
-- @exporter            opentelemetry.trace.exporter.oltp
-- @opts                [optional]
--                          opts.drop_on_queue_full: if true, drop span when queue is full, otherwise force process batches, default true
--                          opts.max_queue_size: maximum queue size to buffer spans for delayed processing, default 2048
--                          opts.batch_timeout: maximum duration for constructing a batch, default 5s
--                          opts.inactive_timeout: maximum duration for processing batches, default 2s
--                          opts.max_export_batch_size: maximum number of spans to process in a single batch, default 256
-- @return              processor
------------------------------------------------------------------
local batch_span_processor = batch_span_processor_new(exporter, {
    drop_on_queue_full = false, max_queue_size = 1024, batch_timeout = 3, inactive_timeout = 1, max_export_batch_size = 10
})

Exporter

Send spans to opentelemetry collector in protobuf format.

local otlp_exporter_new = require("opentelemetry.trace.exporter.otlp").new
local http_client_new = require("opentelemetry.trace.exporter.http_client").new

------------------------------------------------------------------
-- create a http client used by exporter.
--
-- @address             opentelemetry collector: host:port
-- @timeout             export request timeout
-- @headers             export request headers
-- @return              http client
------------------------------------------------------------------
local client = http_client_new("127.0.0.1:4317", 3, {header_key = "header_val"})

local exporter = otlp_exporter_new(client)

Sampling

-- sampling all spans
local always_on_sampler = require("opentelemetry.trace.sampling.always_on_sampler").new()

-- sampling non spans
local always_off_sampler = require("opentelemetry.trace.sampling.always_off_sampler").new()

------------------------------------------------------------------
-- a composite sampler which behaves differently,
-- based on the parent of the span. If the span has no parent,
-- the root(Sampler) is used to make sampling decision. If the span has
-- a parent, depending on whether the parent is sampled.
--
-- @root                sampler
-- @return              sampler
------------------------------------------------------------------
local parent_base_sampler = require("opentelemetry.trace.sampling.parent_base_sampler").new(always_on_sampler)

------------------------------------------------------------------
-- samples a given fraction of traces. Fractions >= 1 will
-- always sample. Fractions < 0 are treated as zero. To respect the
-- parent trace's sampled_flag, the trace_id_ratio_based sampler should be used
-- as a delegate of a parent base sampler.
--
-- @return              sampler
------------------------------------------------------------------
local trace_id_ratio_sampler = require("opentelemetry.trace.sampling.trace_id_ratio_sampler").new(0.5)

Tracer

local span_kind = require("opentelemetry.trace.span_kind")
local attr = require("opentelemetry.attribute")

------------------------------------------------------------------
-- create a span.
--
-- @span_ctx            context with parent span
-- @span_name           span name
-- @span_start_config   [optional]
--                          span_start_config.kind: opentelemetry.trace.span_kind.*
--                          span_start_config.attributes: a list of attribute
-- @return
--                      context: new context with span
--                      span
------------------------------------------------------------------
local context, span = tracer:start(context, name, {kind = span_kind.server, attributes = {attr.string("user", "foo")}})

Trace Context

Implement of specification: https://www.w3.org/TR/trace-context/

local context = require("opentelemetry.context").new()
local trace_context_propagator = require("opentelemetry.trace.propagation.text_map.trace_context_propagator").new()

------------------------------------------------------------------
-- extract span context from upstream request.
--
-- @context             current context
-- @carrier             get traceparent and tracestate
-- @return              new context
------------------------------------------------------------------
local context = trace_context_propagator.extract(context, ngx.req)

Span

local span_kind = require("opentelemetry.trace.span_kind")
local attr = require("opentelemetry.attribute")
local span_status = require("opentelemetry.trace.span_status")
local context, span = tracer:start(context, name, {kind = span_kind.server, attributes = {attr.string("user", "foo")}})

-- get span context
local span_context = span:context()

-- is recording
local ok = span:is_recording()

------------------------------------------------------------------
-- set span status.
--
-- @code             see opentelemetry.trace.span_status.*
-- @message          error msg
------------------------------------------------------------------
span:set_status(span_status.ERROR, "error msg")

-- set attributes
span:set_attributes(attr.string("comapny", "bar"))

-- add a error event
span:record_error("error msg")

------------------------------------------------------------------
-- add a custom event
--
-- @opts           [optional]
--                      opts.attributes: a list of attribute
------------------------------------------------------------------
span:add_event(name, {attributes = {attr.string("type", "job")}})

-- get tracer provider
local tp = span:tracer_provider()

Metrics Reporting

To gather metrics about the operation of this library, users can register a metrics reporter with the opentelemetry.global module. Your metrics reporter should satisfy the interface in lib/opentelemetry/metrics_reporter.lua. For example:

local otel_global = require("opentelemetry.global")
local my_metrics_reporter = {
    add_to_counter = function(self, metric, increment, labels)
        print("make a metric call!")
    end
    record_value = function(self, metric, increment, labels)
        print("make a metric call!")
    end
    observe_value = function(self, metric, increment, labels)
        print("make a metric call!")
    end
}
otel_global.set_metrics_reporter(metrics_reporter)

Benchmarks

You can run benchmarks using make benchmark.