├── .gitignore ├── .busted ├── examples ├── client │ ├── Dockerfile │ ├── go.mod │ └── main.go └── server │ ├── Dockerfile │ ├── go.mod │ └── main.go ├── benchmark ├── run.sh └── id_generator.lua ├── lib └── opentelemetry │ ├── instrumentation_library.lua │ ├── trace │ ├── span_status.lua │ ├── span_kind.lua │ ├── sampling │ │ ├── always_on_sampler.lua │ │ ├── always_off_sampler.lua │ │ ├── result.lua │ │ ├── parent_base_sampler.lua │ │ └── trace_id_ratio_sampler.lua │ ├── propagation │ │ └── text_map │ │ │ ├── getter.lua │ │ │ ├── setter.lua │ │ │ ├── noop_propagator.lua │ │ │ ├── composite_propagator.lua │ │ │ └── trace_context_propagator.lua │ ├── id_generator.lua │ ├── event.lua │ ├── noop_span.lua │ ├── non_recording_span.lua │ ├── simple_span_processor.lua │ ├── span_context.lua │ ├── exporter │ │ ├── console.lua │ │ ├── http_client.lua │ │ ├── encoder.lua │ │ ├── circuit.lua │ │ └── otlp.lua │ ├── tracer.lua │ ├── tracer_provider.lua │ ├── recording_span.lua │ └── tracestate.lua │ ├── semantic_conventions │ └── trace │ │ ├── compatibility.lua │ │ ├── trace.lua │ │ ├── aws.lua │ │ ├── exporter.lua │ │ ├── feature.lua │ │ ├── cloudevents.lua │ │ ├── rpc.lua │ │ ├── faas.lua │ │ ├── database.lua │ │ ├── http.lua │ │ └── general.lua │ ├── global.lua │ ├── api │ ├── README.md │ └── trace │ │ └── span_status.lua │ ├── resource.lua │ ├── metrics_reporter.lua │ ├── attribute.lua │ ├── baggage.lua │ ├── baggage │ └── propagation │ │ └── text_map │ │ └── baggage_propagator.lua │ └── context.lua ├── utils ├── Dockerfile └── generate_semantic_conventions.lua ├── spec ├── api │ ├── trace │ │ └── span_status_spec.lua │ └── spec_helper.lua ├── trace │ ├── span_context_spec.lua │ ├── id_generator_spec.lua │ ├── exporter │ │ └── circuit_spec.lua │ ├── tracestate_spec.lua │ └── propagation │ │ └── text_map │ │ ├── trace_context_propagator_spec.lua │ │ └── composite_propagator_spec.lua ├── baggage_spec.lua └── context_spec.lua ├── e2e-trace-context.sh ├── Dockerfile ├── otel-collector-config.yaml ├── .lua-format ├── t ├── trace │ ├── tracer_provider.t │ ├── span_timestamps.t │ ├── sampling │ │ ├── trace_id_ratio_sampler.t │ │ └── parent_base_sampler.t │ └── tracer.t └── context.t ├── busted-runner ├── docker-compose.yml ├── doc ├── modules │ ├── opentelemetry.html │ └── api.trace.span_status.html └── index.html ├── CONTRIBUTING.md ├── Makefile ├── CHANGELOG.md ├── rockspec ├── opentelemetry-lua-0.1-0.rockspec ├── opentelemetry-lua-0.1-1.rockspec ├── opentelemetry-lua-0.1-2.rockspec ├── opentelemetry-lua-0.1-3.rockspec ├── opentelemetry-lua-0.2-0.rockspec ├── opentelemetry-lua-0.2-1.rockspec ├── opentelemetry-lua-0.2-2.rockspec ├── opentelemetry-lua-0.2-3.rockspec ├── opentelemetry-lua-0.2-4.rockspec ├── opentelemetry-lua-0.2-5.rockspec └── opentelemetry-lua-0.2-6.rockspec └── .github └── workflows └── ci.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | t/servroot 4 | .DS_STORE 5 | -------------------------------------------------------------------------------- /.busted: -------------------------------------------------------------------------------- 1 | return { 2 | _all = { 3 | coverage = false 4 | }, 5 | default = { 6 | verbose = true, 7 | pattern = "_spec" 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /examples/client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.17 2 | COPY . /usr/src/client/ 3 | WORKDIR /usr/src/client/ 4 | RUN go env -w GOPROXY=direct 5 | RUN go install ./main.go 6 | CMD ["/go/bin/main"] -------------------------------------------------------------------------------- /examples/server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.17 2 | COPY . /usr/src/server/ 3 | WORKDIR /usr/src/server/ 4 | RUN go env -w GOPROXY=direct 5 | RUN go install ./main.go 6 | CMD ["/go/bin/main"] -------------------------------------------------------------------------------- /benchmark/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | FILES="benchmark/*.lua" 4 | for f in $FILES 5 | do 6 | if [ -f "$f" ] 7 | then 8 | resty -I /opt/opentelemetry-lua/lib "$f" 9 | fi 10 | done 11 | -------------------------------------------------------------------------------- /lib/opentelemetry/instrumentation_library.lua: -------------------------------------------------------------------------------- 1 | local _M = { 2 | } 3 | 4 | function _M.new(name, version, schema_url) 5 | return {name=name, version=version, schema_url=schema_url} 6 | end 7 | 8 | return _M -------------------------------------------------------------------------------- /benchmark/id_generator.lua: -------------------------------------------------------------------------------- 1 | local id_generator = require("lib.opentelemetry.trace.id_generator") 2 | local start = os.clock() 3 | 4 | for _ = 1, 5000000 do 5 | id_generator.new_ids() 6 | end 7 | 8 | print('5m new ids: ' .. (os.clock() - start) ..' seconds.') 9 | -------------------------------------------------------------------------------- /utils/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openresty/openresty:1.21.4.1-0-centos 2 | 3 | RUN yum install -y gcc gcc-c++ git cmake 4 | RUN luarocks install busted 2.0.0-1 5 | RUN luarocks install ldoc 1.4.6-2 6 | RUN luarocks install --server=https://luarocks.org/dev luaformatter 7 | 8 | WORKDIR /opt/opentelemetry-lua 9 | -------------------------------------------------------------------------------- /spec/api/trace/span_status_spec.lua: -------------------------------------------------------------------------------- 1 | local span_status = require("lib.opentelemetry.api.trace.span_status") 2 | 3 | describe("new()", function() 4 | it("defaults to unset", function() 5 | local s = span_status:new() 6 | assert.are_same(s.code, span_status.UNSET) 7 | end) 8 | end) 9 | -------------------------------------------------------------------------------- /spec/api/spec_helper.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------------------------------------------------ 2 | -- Helps with api tests. Empty for now. 3 | -- @module spec_helper 4 | ------------------------------------------------------------------------------------------------------------------------ 5 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/span_status.lua: -------------------------------------------------------------------------------- 1 | local api_span_status = require("opentelemetry.api.trace.span_status") 2 | 3 | local _M = api_span_status:new() 4 | 5 | -- returns a valid span status code 6 | function _M.validate(code) 7 | if not code or code < 0 or code > 2 then 8 | -- default unset 9 | return 0 10 | end 11 | return code 12 | end 13 | 14 | return _M 15 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/span_kind.lua: -------------------------------------------------------------------------------- 1 | local _M = { 2 | unspecified = 0, 3 | internal = 1, 4 | server = 2, 5 | client = 3, 6 | producer = 4, 7 | consumer = 5, 8 | } 9 | 10 | -- returns a valid span kind value 11 | function _M.validate(kind) 12 | if not kind or kind < 0 or kind > 5 then 13 | -- default internal 14 | return 1 15 | end 16 | return kind 17 | end 18 | 19 | return _M 20 | -------------------------------------------------------------------------------- /lib/opentelemetry/semantic_conventions/trace/compatibility.lua: -------------------------------------------------------------------------------- 1 | --- This file was automatically generated by utils/generate_semantic_conventions.lua 2 | -- See: https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions 3 | -- 4 | -- module @semantic_conventions.trace.compatibility 5 | local _M = { 6 | -- Parent-child Reference type 7 | OPENTRACING_REF_TYPE = "opentracing.ref_type" 8 | } 9 | return _M 10 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/sampling/always_on_sampler.lua: -------------------------------------------------------------------------------- 1 | local result_new = require("opentelemetry.trace.sampling.result").new 2 | 3 | local _M = { 4 | } 5 | 6 | local mt = { 7 | __index = _M 8 | } 9 | 10 | function _M.new() 11 | return setmetatable({}, mt) 12 | end 13 | 14 | function _M.should_sample(self, parameters) 15 | return result_new(2, parameters.parent_ctx:span_context().trace_state) 16 | end 17 | 18 | function _M.description(self) 19 | return "AlwaysOnSampler" 20 | end 21 | 22 | return _M 23 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/sampling/always_off_sampler.lua: -------------------------------------------------------------------------------- 1 | local result_new = require("opentelemetry.trace.sampling.result").new 2 | 3 | local _M = { 4 | } 5 | 6 | local mt = { 7 | __index = _M 8 | } 9 | 10 | function _M.new() 11 | return setmetatable({}, mt) 12 | end 13 | 14 | function _M.should_sample(self, parameters) 15 | return result_new(0, parameters.parent_ctx:span_context().trace_state) 16 | end 17 | 18 | function _M.description(self) 19 | return "AlwaysOffSampler" 20 | end 21 | 22 | return _M 23 | -------------------------------------------------------------------------------- /lib/opentelemetry/semantic_conventions/trace/trace.lua: -------------------------------------------------------------------------------- 1 | --- This file was automatically generated by utils/generate_semantic_conventions.lua 2 | -- See: https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions 3 | -- 4 | -- module @semantic_conventions.trace.trace 5 | local _M = { 6 | -- SHOULD be set to true if the exception event is recorded at a point where it is known that the exception is escaping the scope of the span. 7 | EXCEPTION_ESCAPED = "exception.escaped" 8 | } 9 | return _M 10 | -------------------------------------------------------------------------------- /lib/opentelemetry/semantic_conventions/trace/aws.lua: -------------------------------------------------------------------------------- 1 | --- This file was automatically generated by utils/generate_semantic_conventions.lua 2 | -- See: https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions 3 | -- 4 | -- module @semantic_conventions.trace.aws 5 | local _M = { 6 | -- The full invoked ARN as provided on the `Context` passed to the function (`Lambda-Runtime-Invoked-Function-Arn` header on the `/runtime/invocation/next` applicable). 7 | AWS_LAMBDA_INVOKED_ARN = "aws.lambda.invoked_arn" 8 | } 9 | return _M 10 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/propagation/text_map/getter.lua: -------------------------------------------------------------------------------- 1 | local _M = { 2 | } 3 | 4 | local mt = { 5 | __index = _M 6 | } 7 | 8 | function _M.new() 9 | return setmetatable({}, mt) 10 | end 11 | 12 | ------------------------------------------------------------------ 13 | -- Extract tracing header from nginx request 14 | -- 15 | -- @param carrier (should be ngx.req) 16 | -- @param key HTTP header to get 17 | -- @return value of HTTP header 18 | ------------------------------------------------------------------ 19 | function _M.get(carrier, key) 20 | return carrier.get_headers()[key] 21 | end 22 | 23 | return _M 24 | -------------------------------------------------------------------------------- /lib/opentelemetry/semantic_conventions/trace/exporter.lua: -------------------------------------------------------------------------------- 1 | --- This file was automatically generated by utils/generate_semantic_conventions.lua 2 | -- See: https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions 3 | -- 4 | -- module @semantic_conventions.trace.exporter 5 | local _M = { 6 | -- Name of the code, either "OK" or "ERROR". MUST NOT be set if the status code is UNSET. 7 | OTEL_STATUS_CODE = "otel.status_code", 8 | -- Description of the Status if it has a value, otherwise not set. 9 | OTEL_STATUS_DESCRIPTION = "otel.status_description" 10 | } 11 | return _M 12 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/propagation/text_map/setter.lua: -------------------------------------------------------------------------------- 1 | local _M = { 2 | } 3 | 4 | local mt = { 5 | __index = _M 6 | } 7 | 8 | function _M.new() 9 | return setmetatable({}, mt) 10 | end 11 | 12 | ------------------------------------------------------------------ 13 | -- Add tracing information to nginx request as headers 14 | -- 15 | -- @param carrier nginx request 16 | -- @param key HTTP header to set 17 | -- @param val value of HTTP header 18 | -- @return nil 19 | ------------------------------------------------------------------ 20 | function _M.set(carrier, name, val) 21 | carrier.set_header(name, val) 22 | end 23 | 24 | return _M 25 | -------------------------------------------------------------------------------- /spec/trace/span_context_spec.lua: -------------------------------------------------------------------------------- 1 | local span_context = require("opentelemetry.trace.span_context") 2 | 3 | describe("is_valid", function() 4 | it("returns false when traceid == 00000000000000000000000000000000", function() 5 | local sp_ctx = span_context.new("00000000000000000000000000000000", "1234567890123456", 1, nil, false) 6 | assert.is_not_true(sp_ctx:is_valid()) 7 | end) 8 | 9 | it("returns false when spanid == 0000000000000000", function() 10 | local sp_ctx = span_context.new("00000000000000000000000000000001", "0000000000000000", 1, nil, false) 11 | assert.is_not_true(sp_ctx:is_valid()) 12 | end) 13 | end) 14 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/id_generator.lua: -------------------------------------------------------------------------------- 1 | local util = require "opentelemetry.util" 2 | local bit = require 'bit' 3 | 4 | local tohex = bit.tohex 5 | local fmt = string.format 6 | local random = util.random 7 | 8 | local _M = {} 9 | 10 | function _M.new_span_id() 11 | return fmt("%s%s", 12 | tohex(random(0, 0xFFFFFFFF), 8), 13 | tohex(random(0, 0xFFFFFFFF), 8)) 14 | end 15 | 16 | function _M.new_ids() 17 | return fmt("%s%s%s%s", 18 | tohex(random(0, 0xFFFFFFFF), 8), 19 | tohex(random(0, 0xFFFFFFFF), 8), 20 | tohex(random(0, 0xFFFFFFFF), 8), 21 | tohex(random(0, 0xFFFFFFFF), 8)), _M.new_span_id() 22 | end 23 | 24 | return _M 25 | -------------------------------------------------------------------------------- /spec/trace/id_generator_spec.lua: -------------------------------------------------------------------------------- 1 | local id_generator = require("opentelemetry.trace.id_generator") 2 | 3 | describe("new_span_id", function() 4 | it("generates a 16 character hex string", function() 5 | for _ = 0, 100 do 6 | assert.is_equal(16, #id_generator.new_span_id()) 7 | end 8 | end) 9 | end) 10 | 11 | describe("new_ids", function() 12 | it("generates a 16 character hex string and a 32 character string", function() 13 | for _ = 0, 100 do 14 | local trace_id, span_id = id_generator.new_ids() 15 | assert.is_equal(32, #trace_id) 16 | assert.is_equal(16, #span_id) 17 | end 18 | end) 19 | end) 20 | -------------------------------------------------------------------------------- /e2e-trace-context.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ################################################################################ 4 | # There's a test suite in the w3c/trace-context repo that we use against our 5 | # code and nginx.conf 6 | ################################################################################ 7 | 8 | cd /opt 9 | if [ ! -d "trace-context" ]; then 10 | git clone https://github.com/w3c/trace-context 11 | fi 12 | cd trace-context/test 13 | 14 | # Run an individual test: 15 | # HARNESS_DEBUG=1 python3 test.py http://127.0.0.1:80/test/e2e/trace-context TraceContextTest.test_traceparent_parent_id_too_short 16 | 17 | # Run every test: 18 | python3 test.py http://127.0.0.1:80/test/e2e/trace-context 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openresty/openresty:1.21.4.1-0-centos 2 | 3 | RUN yum install -y gcc 4 | RUN yum -y --enablerepo=powertools install libyaml-devel libffi-devel 5 | RUN luarocks install lua-resty-http 0.16.1-0 6 | RUN luarocks install lua-protobuf 0.3.3 7 | RUN luarocks install net-url 1.1-1 8 | RUN luarocks install busted 2.0.0-1 9 | RUN luarocks --server=http://rocks.moonscript.org install lyaml 10 | 11 | RUN yum install -y cpanminus perl 12 | RUN cpanm --notest Test::Nginx IPC::Run > build.log 2>&1 || (cat build.log && exit 1) 13 | 14 | RUN yum install -y python3 python3-devel git 15 | RUN pip3 install multidict attrs yarl async_timeout charset-normalizer idna_ssl aiosignal 16 | RUN pip3 install aiohttp 17 | 18 | WORKDIR /opt/opentelemetry-lua 19 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/event.lua: -------------------------------------------------------------------------------- 1 | local util = require("opentelemetry.util") 2 | 3 | local _M = { 4 | } 5 | 6 | local mt = { 7 | __index = _M 8 | } 9 | 10 | ------------------------------------------------------------------ 11 | -- create a event. 12 | -- 13 | -- @name event name 14 | -- @opts [optional] 15 | -- opts.attributes: a list of attribute 16 | -- @return event 17 | ------------------------------------------------------------------ 18 | function _M.new(name, opts) 19 | local self = { 20 | name = name, 21 | attributes = opts.attributes, 22 | time_unix_nano = string.format("%d", util.time_nano()) 23 | } 24 | return setmetatable(self, mt) 25 | end 26 | 27 | return _M 28 | -------------------------------------------------------------------------------- /lib/opentelemetry/semantic_conventions/trace/feature.lua: -------------------------------------------------------------------------------- 1 | --- This file was automatically generated by utils/generate_semantic_conventions.lua 2 | -- See: https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions 3 | -- 4 | -- module @semantic_conventions.trace.feature 5 | local _M = { 6 | -- The unique identifier of the feature flag. 7 | FEATURE_FLAG_KEY = "feature_flag.key", 8 | -- The name of the service provider that performs the flag evaluation. 9 | FEATURE_FLAG_PROVIDER_NAME = "feature_flag.provider_name", 10 | -- SHOULD be a semantic identifier for a value. If one is unavailable, a stringified version of the value can be used. 11 | FEATURE_FLAG_VARIANT = "feature_flag.variant" 12 | } 13 | return _M 14 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/noop_span.lua: -------------------------------------------------------------------------------- 1 | local empty_span_context = require("opentelemetry.trace.span_context") 2 | 3 | local _M = { 4 | } 5 | 6 | local mt = { 7 | __index = _M 8 | } 9 | 10 | function _M.new() 11 | return setmetatable({ ctx = empty_span_context.new() }, mt) 12 | end 13 | 14 | function _M.context(self) 15 | return self.ctx 16 | end 17 | 18 | function _M.is_recording() 19 | return false 20 | end 21 | 22 | function _M.set_status() 23 | end 24 | 25 | function _M.set_attributes() 26 | end 27 | 28 | function _M.finish(_self, _end_timestamp) 29 | end 30 | 31 | function _M.record_error() 32 | end 33 | 34 | function _M.add_event() 35 | end 36 | 37 | function _M.set_name() 38 | end 39 | 40 | function _M.tracer_provider() 41 | end 42 | 43 | return _M 44 | -------------------------------------------------------------------------------- /lib/opentelemetry/global.lua: -------------------------------------------------------------------------------- 1 | local metrics_reporter = require("opentelemetry.metrics_reporter") 2 | 3 | local _M = { context_storage = nil, metrics_reporter = metrics_reporter } 4 | 5 | function _M.set_tracer_provider(tp) 6 | _M.tracer_provider = tp 7 | end 8 | 9 | function _M.get_tracer_provider() 10 | return _M.tracer_provider 11 | end 12 | 13 | function _M.set_metrics_reporter(metrics_reporter) 14 | _M.metrics_reporter = metrics_reporter 15 | end 16 | 17 | function _M.tracer(name, opts) 18 | return _M.tracer_provider:tracer(name, opts) 19 | end 20 | 21 | function _M.get_context_storage() 22 | return _M.context_storage or ngx.ctx 23 | end 24 | 25 | function _M.set_context_storage(context_storage) 26 | _M.context_storage = context_storage 27 | end 28 | 29 | return _M 30 | -------------------------------------------------------------------------------- /lib/opentelemetry/api/README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | This folder contains an in-progress implementation of OpenTelemetry's API. All operations effectively no-op unless the program registers an SDK. For more on the API/SDK distinction, check out the [documentation](https://opentelemetry.io/docs/reference/specification/overview/#api). 4 | 5 | ## Dev dependencies 6 | 7 | `lua-formatter`, `busted`, `ldoc`. 8 | 9 | ## Running tests 10 | 11 | Run `make api-test` from root of repository. 12 | 13 | Run a single test by adding `#now` (or another tag of your choosing) to the test description `it("foo bar #now")` and then running `busted -m "./lib/?.lua;./lib/?/?.lua;./lib/?/?/?.lua" -t "now" spec/api` 14 | 15 | ## Generating docs 16 | 17 | `make doc` 18 | 19 | ## Formatting 20 | 21 | `make format` (we use [`lua-formatter`](https://github.com/Koihik/LuaFormatter) to format the code). 22 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/non_recording_span.lua: -------------------------------------------------------------------------------- 1 | local _M = { 2 | } 3 | 4 | local mt = { 5 | __index = _M 6 | } 7 | 8 | function _M.new(tracer, ctx) 9 | local self = { 10 | tracer = tracer, 11 | ctx = ctx, 12 | } 13 | return setmetatable(self, mt) 14 | end 15 | 16 | function _M.context(self) 17 | return self.ctx 18 | end 19 | 20 | function _M.is_recording() 21 | return false 22 | end 23 | 24 | function _M.set_status() 25 | end 26 | 27 | function _M.set_attributes() 28 | end 29 | 30 | function _M.finish(_self, _end_timestamp) 31 | end 32 | 33 | function _M.record_error() 34 | end 35 | 36 | function _M.add_event() 37 | end 38 | 39 | function _M.set_name() 40 | end 41 | 42 | function _M.tracer_provider(self) 43 | return self.tracer.provider 44 | end 45 | 46 | function _M.plain(self) 47 | return { 48 | ctx = self.ctx 49 | } 50 | end 51 | 52 | return _M 53 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/sampling/result.lua: -------------------------------------------------------------------------------- 1 | local _M = { 2 | drop = 0, 3 | record_only = 1, 4 | record_and_sample = 2 5 | } 6 | 7 | local mt = { 8 | __index = _M 9 | } 10 | 11 | ------------------------------------------------------------------ 12 | -- create a sample result. 13 | -- 14 | -- @decision 0: do nothing 15 | -- 1: recording 16 | -- 2: recording and sampling 17 | -- @return sample result 18 | ------------------------------------------------------------------ 19 | function _M.new(decision, trace_state) 20 | return setmetatable({decision = decision, trace_state = trace_state}, mt) 21 | end 22 | 23 | function _M.is_sampled(self) 24 | return self.decision == self.record_and_sample 25 | end 26 | 27 | function _M.is_recording(self) 28 | return self.decision == self.record_only or self.decision == self.record_and_sample 29 | end 30 | 31 | return _M 32 | -------------------------------------------------------------------------------- /otel-collector-config.yaml: -------------------------------------------------------------------------------- 1 | receivers: 2 | otlp: 3 | protocols: 4 | http: 5 | endpoint: 0.0.0.0:4317 6 | 7 | exporters: 8 | prometheus: 9 | endpoint: "0.0.0.0:8889" 10 | const_labels: 11 | label1: value1 12 | debug: 13 | verbosity: detailed 14 | 15 | #zipkin: 16 | # endpoint: "http://zipkin-all-in-one:9411/api/v2/spans" 17 | # format: proto 18 | 19 | otlp/jaeger: 20 | endpoint: jaeger:4317 21 | tls: 22 | insecure: true 23 | 24 | processors: 25 | batch: 26 | 27 | extensions: 28 | health_check: 29 | pprof: 30 | endpoint: :1888 31 | zpages: 32 | endpoint: :55679 33 | 34 | service: 35 | extensions: [pprof, zpages, health_check] 36 | pipelines: 37 | traces: 38 | receivers: [otlp] 39 | processors: [batch] 40 | exporters: [debug, otlp/jaeger] 41 | metrics: 42 | receivers: [otlp] 43 | processors: [batch] 44 | exporters: [debug, prometheus] -------------------------------------------------------------------------------- /.lua-format: -------------------------------------------------------------------------------- 1 | column_limit: 120 2 | indent_width: 4 3 | use_tab: false 4 | tab_width: 4 5 | continuation_indent_width: 4 6 | spaces_before_call: 1 7 | keep_simple_control_block_one_line: false 8 | keep_simple_function_one_line: false 9 | align_args: true 10 | break_after_functioncall_lp: false 11 | break_before_functioncall_rp: false 12 | spaces_inside_functioncall_parens: false 13 | spaces_inside_functiondef_parens: false 14 | align_parameter: true 15 | chop_down_parameter: false 16 | break_after_functiondef_lp: false 17 | break_before_functiondef_rp: false 18 | align_table_field: true 19 | break_after_table_lb: true 20 | break_before_table_rb: true 21 | chop_down_table: false 22 | chop_down_kv_table: true 23 | table_sep: "," 24 | column_table_limit: 0 25 | extra_sep_at_table_end: false 26 | spaces_inside_table_braces: true 27 | break_after_operator: true 28 | double_quote_to_single_quote: false 29 | single_quote_to_double_quote: false 30 | spaces_around_equals_in_field: true 31 | line_breaks_after_function_body: 1 32 | line_separator: input 33 | -------------------------------------------------------------------------------- /lib/opentelemetry/api/trace/span_status.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------------------------------------------------ 2 | -- Span status represents the status of a span. Like an HTTP code, but for your span. It is either unset, ok, or error. 3 | -- 4 | -- @module api.trace.span_status 5 | ------------------------------------------------------------------------------------------------------------------------ 6 | local _M = { UNSET = 0, OK = 1, ERROR = 2 } 7 | 8 | ------------------------------------------------------------------------------------------------------------------------ 9 | -- Returns a new span_status. 10 | -- 11 | -- @param[type=int] code The status code. Defaults to UNSET. 12 | ------------------------------------------------------------------------------------------------------------------------ 13 | function _M:new(code) 14 | local ret = { code = code or _M.UNSET, description = nil } 15 | setmetatable(ret, { __index = self }) 16 | self.__index = self 17 | return ret 18 | end 19 | 20 | return _M 21 | -------------------------------------------------------------------------------- /t/trace/tracer_provider.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | log_level('debug'); 4 | repeat_each(1); 5 | no_long_string(); 6 | no_root_location(); 7 | run_tests(); 8 | 9 | __DATA__ 10 | 11 | === TEST 1: tracer provider force_flush and shutdown 12 | --- config 13 | location = /t { 14 | content_by_lua_block { 15 | local tracer_provider_new = require("opentelemetry.trace.tracer_provider").new 16 | local fake_processor = { 17 | force_flush = function() 18 | ngx.say("call span processor force_flush") 19 | end, 20 | shutdown = function() 21 | ngx.say("call span processor shutdown") 22 | end 23 | } 24 | local tracer_provider = tracer_provider_new(fake_processor) 25 | tracer_provider:force_flush() 26 | tracer_provider:shutdown() 27 | ngx.say("done") 28 | } 29 | } 30 | --- request 31 | GET /t 32 | --- error_code: 200 33 | --- response_body 34 | call span processor force_flush 35 | call span processor shutdown 36 | done 37 | --- no_error_log 38 | [error] -------------------------------------------------------------------------------- /lib/opentelemetry/resource.lua: -------------------------------------------------------------------------------- 1 | local _M = { 2 | } 3 | 4 | local mt = { 5 | __index = _M 6 | } 7 | 8 | ------------------------------------------------------------------ 9 | -- create a resource. 10 | -- 11 | -- @decision attribute1, attribute2, attribute3, ... 12 | -- @return resource 13 | ------------------------------------------------------------------ 14 | function _M.new(...) 15 | local self = { 16 | attrs = {...} 17 | } 18 | return setmetatable(self, mt) 19 | end 20 | 21 | function _M.attributes(self) 22 | return self.attrs 23 | end 24 | 25 | function _M.merge(a, b) 26 | if a == nil then 27 | return b 28 | end 29 | 30 | local b_attr_keys = {} 31 | local new_attrs = {} 32 | for _, attr in ipairs(b.attrs) do 33 | table.insert(new_attrs, attr) 34 | b_attr_keys[attr.key] = true 35 | end 36 | for _, attr in ipairs(a.attrs) do 37 | if not b_attr_keys[attr.key] then 38 | table.insert(new_attrs, attr) 39 | end 40 | end 41 | 42 | return setmetatable({attrs = new_attrs}, mt) 43 | end 44 | 45 | return _M -------------------------------------------------------------------------------- /examples/client/go.mod: -------------------------------------------------------------------------------- 1 | module client 2 | 3 | go 1.17 4 | 5 | require ( 6 | go.opentelemetry.io/otel v1.3.0 7 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0 8 | go.opentelemetry.io/otel/sdk v1.3.0 9 | go.opentelemetry.io/otel/trace v1.3.0 10 | ) 11 | 12 | require ( 13 | github.com/cenkalti/backoff/v4 v4.1.2 // indirect 14 | github.com/go-logr/logr v1.2.1 // indirect 15 | github.com/go-logr/stdr v1.2.0 // indirect 16 | github.com/golang/protobuf v1.5.2 // indirect 17 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect 18 | go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0 // indirect 19 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 // indirect 20 | go.opentelemetry.io/proto/otlp v0.11.0 // indirect 21 | golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect 22 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 // indirect 23 | golang.org/x/text v0.3.0 // indirect 24 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect 25 | google.golang.org/grpc v1.42.0 // indirect 26 | google.golang.org/protobuf v1.27.1 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /examples/server/go.mod: -------------------------------------------------------------------------------- 1 | module server 2 | 3 | go 1.17 4 | 5 | require ( 6 | go.opentelemetry.io/otel v1.3.0 7 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0 8 | go.opentelemetry.io/otel/sdk v1.3.0 9 | go.opentelemetry.io/otel/trace v1.3.0 10 | ) 11 | 12 | require ( 13 | github.com/cenkalti/backoff/v4 v4.1.2 // indirect 14 | github.com/go-logr/logr v1.2.1 // indirect 15 | github.com/go-logr/stdr v1.2.0 // indirect 16 | github.com/golang/protobuf v1.5.2 // indirect 17 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect 18 | go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0 // indirect 19 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 // indirect 20 | go.opentelemetry.io/proto/otlp v0.11.0 // indirect 21 | golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect 22 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 // indirect 23 | golang.org/x/text v0.3.0 // indirect 24 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect 25 | google.golang.org/grpc v1.42.0 // indirect 26 | google.golang.org/protobuf v1.27.1 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /busted-runner: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env resty 2 | 3 | package.path = './lib/?.lua;./lib/?/?.lua;./lib/?/init.lua' .. package.path 4 | 5 | -- Set up global tracer 6 | Global = require("opentelemetry.global") 7 | local tracer_provider = require("opentelemetry.trace.tracer_provider") 8 | local attr = require("opentelemetry.attribute") 9 | local resource = require("opentelemetry.resource") 10 | local always_on_sampler = require("opentelemetry.trace.sampling.always_on_sampler") 11 | local batch_span_processor = require("opentelemetry.trace.batch_span_processor") 12 | local exporter = require("opentelemetry.trace.exporter.console") 13 | local processor = batch_span_processor.new(exporter, { 14 | drop_on_queue_full = false, max_queue_size = 1024, batch_timeout = 3, inactive_timeout = 1, max_export_batch_size = 10 15 | }) 16 | local tracer_provider = tracer_provider.new(processor, { 17 | sampler = always_on_sampler, 18 | resource = resource.new(attr.string("service.name", "openresty"), attr.int("attr_int", 100)), 19 | }) 20 | Global.set_tracer_provider(tracer_provider) 21 | 22 | if ngx ~= nil then 23 | ngx.exit = function() end 24 | end 25 | 26 | -- Busted command-line runner 27 | require 'busted.runner' ( 28 | { 29 | standalone = false, 30 | } 31 | ) 32 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/simple_span_processor.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- The simple span processor immediately exports spans as they finish. It does 3 | -- not batch spans or process the spans in a background thread (as the batch 4 | -- span processor does via ngx.timer.at). It is intended to be used for 5 | -- debugging. 6 | -------------------------------------------------------------------------------- 7 | 8 | local _M = { 9 | } 10 | 11 | local mt = { 12 | __index = _M 13 | } 14 | 15 | -------------------------------------------------------------------------------- 16 | -- create a simple span processor. 17 | -- 18 | -- @param exporter an exporter that will be used to send span data to its 19 | -- destination. 20 | -- @return processor 21 | -------------------------------------------------------------------------------- 22 | function _M.new(exporter) 23 | return setmetatable({ 24 | exporter = exporter, 25 | }, mt) 26 | end 27 | 28 | function _M.on_end(self, span) 29 | if not span.ctx:is_sampled() or self.closed then 30 | return 31 | end 32 | 33 | self.exporter:export_spans({ span }) 34 | end 35 | 36 | function _M.shutdown(self) 37 | self.closed = true 38 | self.exporter:shutdown() 39 | end 40 | 41 | return _M 42 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | openresty: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile 6 | volumes: 7 | - ./examples/openresty/nginx.conf:/etc/nginx/conf.d/default.conf 8 | - .:/opt/opentelemetry-lua 9 | depends_on: 10 | - otel-collector 11 | - test-server 12 | networks: 13 | - opentelemetry-lua 14 | ports: 15 | - 80:80 16 | jaeger: 17 | image: jaegertracing/all-in-one:1 18 | ports: 19 | - 26686:16686 20 | networks: 21 | - opentelemetry-lua 22 | otel-collector: 23 | image: otel/opentelemetry-collector-contrib:0.98.0 24 | command: [ "--config=/etc/otel-collector-config.yaml" ] 25 | volumes: 26 | - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml 27 | depends_on: 28 | - jaeger 29 | networks: 30 | - opentelemetry-lua 31 | test-server: 32 | build: 33 | context: ./examples/server 34 | depends_on: 35 | - otel-collector 36 | networks: 37 | - opentelemetry-lua 38 | test-client: 39 | build: 40 | context: ./examples/client 41 | environment: 42 | - PROXY_ENDPOINT=${PROXY_ENDPOINT} 43 | networks: 44 | - opentelemetry-lua 45 | utils: 46 | build: 47 | context: ./utils 48 | volumes: 49 | - .:/opt/opentelemetry-lua 50 | networks: 51 | opentelemetry-lua: 52 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/propagation/text_map/noop_propagator.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- The noop propagator does nothing. It should be the default propagator for the 3 | -- API 4 | -------------------------------------------------------------------------------- 5 | local _M = { 6 | } 7 | 8 | local mt = { 9 | __index = _M, 10 | } 11 | 12 | function _M.new() 13 | return setmetatable({}, mt) 14 | end 15 | 16 | -------------------------------------------------------------------------------- 17 | -- noop injection 18 | -- 19 | -- @param _context context storage 20 | -- @param _carrier nginx request 21 | -- @param _setter setter for interacting with carrier 22 | -- @return nil 23 | -------------------------------------------------------------------------------- 24 | function _M:inject(_context, _carrier, _setter) 25 | end 26 | 27 | -------------------------------------------------------------------------------- 28 | -- noop extraction 29 | -- 30 | -- @param context context storage 31 | -- @param _carrier nginx request 32 | -- @param _getter getter for interacting with carrier 33 | -- @return nil 34 | -------------------------------------------------------------------------------- 35 | function _M:extract(context, _carrier, _getter) 36 | return context 37 | end 38 | 39 | return _M 40 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/span_context.lua: -------------------------------------------------------------------------------- 1 | local tracestate = require("opentelemetry.trace.tracestate") 2 | local _M = { 3 | INVALID_TRACE_ID = "00000000000000000000000000000000", 4 | INVALID_SPAN_ID = "0000000000000000" 5 | } 6 | 7 | local mt = { 8 | __index = _M 9 | } 10 | 11 | function _M.new(tid, sid, trace_flags, trace_state, remote) 12 | local self = { 13 | trace_id = tid, 14 | span_id = sid, 15 | trace_flags = trace_flags, 16 | trace_state = trace_state or tracestate.new({}), 17 | remote = remote, 18 | } 19 | return setmetatable(self, mt) 20 | end 21 | 22 | function _M.is_valid(self) 23 | if self.trace_id == _M.INVALID_TRACE_ID or self.trace_id == nil then 24 | return false 25 | end 26 | 27 | if self.span_id == _M.INVALID_SPAN_ID or self.span_id == nil then 28 | return false 29 | end 30 | 31 | return true 32 | end 33 | 34 | function _M.is_remote(self) 35 | return self.remote 36 | end 37 | 38 | function _M.is_sampled(self) 39 | return bit.band(self.trace_flags, 1) == 1 40 | end 41 | 42 | function _M.plain(self) 43 | return { 44 | trace_id = self.trace_id, 45 | span_id = self.span_id, 46 | trace_flags = self.trace_flags, 47 | trace_state = self.trace_state, 48 | remote = self.remote, 49 | } 50 | end 51 | 52 | return _M 53 | -------------------------------------------------------------------------------- /t/trace/span_timestamps.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | log_level('debug'); 4 | repeat_each(1); 5 | no_long_string(); 6 | no_root_location(); 7 | run_tests(); 8 | 9 | __DATA__ 10 | 11 | === TEST 1: span start and end timestamps can be set explicitly 12 | --- config 13 | location = /t { 14 | content_by_lua_block { 15 | local tracer_provider = require("opentelemetry.trace.tracer_provider").new() 16 | local context = require("opentelemetry.context").new() 17 | local span_context_new = require("opentelemetry.trace.span_context").new 18 | local span_kind = require("opentelemetry.trace.span_kind") 19 | local attr = require("opentelemetry.attribute") 20 | local tracer = tracer_provider:tracer("unit_test") 21 | local context, recording_span = tracer:start(context, "recording", 22 | {kind = span_kind.producer, attributes = {attr.string("key", "value")}}, 123456788) 23 | context.sp:finish(123456789) 24 | if context.sp.start_time ~= 123456788 then 25 | ngx.log(ngx.ERR, "start time should have been 123456788, was " .. context.sp.start_time) 26 | end 27 | if context.sp.end_time ~= 123456789 then 28 | ngx.log(ngx.ERR, "end time should have been 123456789, was " .. context.sp.end_time) 29 | end 30 | } 31 | } 32 | --- request 33 | GET /t 34 | --- no_error_log 35 | 123456789 36 | -------------------------------------------------------------------------------- /lib/opentelemetry/semantic_conventions/trace/cloudevents.lua: -------------------------------------------------------------------------------- 1 | --- This file was automatically generated by utils/generate_semantic_conventions.lua 2 | -- See: https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions 3 | -- 4 | -- module @semantic_conventions.trace.cloudevents 5 | local _M = { 6 | -- The [event_id](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#id) uniquely identifies the event. 7 | CLOUDEVENTS_EVENT_ID = "cloudevents.event_id", 8 | -- The [source](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#source-1) identifies the context in which an event happened. 9 | CLOUDEVENTS_EVENT_SOURCE = "cloudevents.event_source", 10 | -- The [version of the CloudEvents specification](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#specversion) which the event uses. 11 | CLOUDEVENTS_EVENT_SPEC_VERSION = "cloudevents.event_spec_version", 12 | -- The [event_type](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#type) contains a value describing the type of event related to the originating occurrence. 13 | CLOUDEVENTS_EVENT_TYPE = "cloudevents.event_type", 14 | -- The [subject](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#subject) of the event in the context of the event producer (identified by source). 15 | CLOUDEVENTS_EVENT_SUBJECT = "cloudevents.event_subject" 16 | } 17 | return _M 18 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/exporter/console.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------- 2 | -- The console span exporter is used for debugging. It should not be used in 3 | -- production contexts. 4 | ------------------------------------------------------------------------------- 5 | local encoder = require("opentelemetry.trace.exporter.encoder") 6 | 7 | local _M = { 8 | } 9 | 10 | local mt = { 11 | __index = _M 12 | } 13 | 14 | -------------------------------------------------------------------------------- 15 | -- Create a new console span exporter. If being run in an nginx context, will 16 | -- log spans to ngx.log(ngx.INFO). Otherwise, will use Lua's print() method. 17 | -- 18 | -- @return New console span exporter 19 | -------------------------------------------------------------------------------- 20 | function _M.new() 21 | return setmetatable({}, mt) 22 | end 23 | 24 | function _M.export_spans(self, spans) 25 | local span_string = "" 26 | for _, span in ipairs(spans) do 27 | span_string = span_string .. encoder.for_console(span) .. "\n" 28 | end 29 | 30 | -- Check if ngx variable is not nil; use ngx.log if ngx var is present. 31 | if ngx then 32 | ngx.log(ngx.INFO, "Export spans: ", span_string) 33 | else 34 | print("Export spans: ", span_string) 35 | end 36 | end 37 | 38 | function _M.force_flush(self) 39 | end 40 | 41 | function _M.shutdown(self) 42 | end 43 | 44 | return _M 45 | -------------------------------------------------------------------------------- /spec/baggage_spec.lua: -------------------------------------------------------------------------------- 1 | local baggage = require("opentelemetry.baggage") 2 | 3 | describe("set_value and get_value", function() 4 | it("sets a value and returns baggage instance", function() 5 | local baggage = baggage.new({ oldkey = { value = "wat" } }) 6 | local new_baggage = baggage:set_value("keyname", "val", "metadatastring") 7 | assert.are.equal(new_baggage:get_value("keyname"), "val") 8 | assert.are.equal(new_baggage:get_value("oldkey"), "wat") 9 | end) 10 | 11 | it("overwrites keys", function() 12 | local baggage = baggage.new({ oldkey = { value = "wat" } }) 13 | local new_baggage = baggage:set_value("oldkey", "newvalue") 14 | assert.are.equal(new_baggage:get_value("oldkey"), "newvalue") 15 | end) 16 | end) 17 | 18 | describe("get__all_values", function() 19 | it("returns all values", function() 20 | local values = { key1 = { value = "wat", metadata = "ignore" }, key2 = { value = "wat2", metadata } } 21 | local baggage = baggage.new(values) 22 | assert.are.same(baggage:get_all_values(), values) 23 | end) 24 | end) 25 | 26 | describe("remove_value", function() 27 | it("returns new baggage instance without value", function() 28 | local values = { key1 = { value = "wat" }, key2 = { value = "wat2" } } 29 | local baggage = baggage.new(values) 30 | local new_baggage = baggage:remove_value("key1") 31 | assert.are.same(new_baggage:get_all_values(), { key2 = { value = "wat2" } }) 32 | end) 33 | end) 34 | -------------------------------------------------------------------------------- /doc/modules/opentelemetry.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 42 | 43 |
44 | 45 |

Module opentelemetry

46 |

The entrypoint for the package.

47 |

48 | !!! Requiring this file sets a global of __OTEL!

49 | 50 | 51 | 52 |
53 |
54 | 55 | 56 | 57 | 58 |
59 |
60 |
61 | generated by LDoc 1.4.6 62 | Last updated 2023-01-18 22:27:32 63 |
64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/sampling/parent_base_sampler.lua: -------------------------------------------------------------------------------- 1 | local result_new = require("opentelemetry.trace.sampling.result").new 2 | 3 | local _M = { 4 | } 5 | 6 | local mt = { 7 | __index = _M 8 | } 9 | 10 | ------------------------------------------------------------------ 11 | -- a composite sampler which behaves differently, 12 | -- based on the parent of the span. If the span has no parent, 13 | -- the root(Sampler) is used to make sampling decision. If the span has 14 | -- a parent, depending on whether the parent is sampled. 15 | -- 16 | -- @root sampler 17 | -- @return sampler 18 | ------------------------------------------------------------------ 19 | function _M.new(root) 20 | return setmetatable({root = root}, mt) 21 | end 22 | 23 | function _M.should_sample(self, parameters) 24 | local parent_span_ctx = parameters.parent_ctx:span_context() 25 | local trace_state = parent_span_ctx.trace_state 26 | if parent_span_ctx:is_valid() then 27 | if parent_span_ctx:is_remote() then 28 | if parent_span_ctx:is_sampled() then 29 | return result_new(2, trace_state) 30 | end 31 | return result_new(0, trace_state) 32 | end 33 | 34 | if parent_span_ctx:is_sampled() then 35 | return result_new(2, trace_state) 36 | end 37 | return result_new(0, trace_state) 38 | end 39 | 40 | return self.root:should_sample(parameters) 41 | end 42 | 43 | function _M.description(self) 44 | return string.format("ParentBased{root:%s}", self.root:description()) 45 | end 46 | 47 | return _M 48 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Running Tests 4 | 5 | There are a few different types of tests in this repo, which can all be run via the Makefile. 6 | 7 | ### Integration tests 8 | 9 | There are integration tests in the `t/` directory. These use Perl's `Test::NGINX` framework, as described in the [Programming Openresty Book](https://openresty.gitbooks.io/programming-openresty/content/testing/test-nginx.html). To run these tests, you first must start a working openresty server, which is configured to use the code in the repo (`make openresty-dev`). Then, you can run tests using `make openresty-unit-test`. 10 | 11 | ``` 12 | make openresty-build 13 | make openresty-dev 14 | make openresty-unit-test 15 | make openresty-test-e2e 16 | ``` 17 | 18 | To pick up code changes, you need to re-run `luarocks make && nginx -s reload` inside the `openresty` container started by `make openresty-dev`. 19 | 20 | ### Tracecontext tests 21 | 22 | There's a test suite in the [w3c/trace-context repo](https://github.com/w3c/trace-context/) that we run against our code and nginx.conf. To run these: 23 | 24 | ``` 25 | make openresty-dev 26 | make openresty-test-e2e-trace-context 27 | ``` 28 | 29 | ### Unit tests 30 | 31 | There's two sets of unit tests oriented around different runtimes. 32 | 33 | ``` 34 | # Run tests oriented around Openresty 35 | make lua-unit-test 36 | 37 | # Run tests that should pass in any Lua runtime 38 | make api-test 39 | ``` 40 | 41 | 42 | ## Community 43 | 44 | This project is not officially part of the OpenTelemetry org yet, but you can find some folks in this [Slack channel](https://cloud-native.slack.com/archives/C048T6NFQTY) in [CNCF Slack](https://communityinviter.com/apps/cloud-native/cncf). 45 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/sampling/trace_id_ratio_sampler.lua: -------------------------------------------------------------------------------- 1 | local result_new = require("opentelemetry.trace.sampling.result").new 2 | local always_on_sampler_new = require("opentelemetry.trace.sampling.always_on_sampler").new 3 | 4 | local _M = { 5 | } 6 | 7 | local mt = { 8 | __index = _M 9 | } 10 | 11 | ------------------------------------------------------------------ 12 | -- samples a given fraction of traces. Fractions >= 1 will 13 | -- always sample. Fractions < 0 are treated as zero. To respect the 14 | -- parent trace's sampled_flag, the trace_id_ratio_based sampler should be used 15 | -- as a delegate of a parent base sampler. 16 | -- 17 | -- @return sampler 18 | ------------------------------------------------------------------ 19 | function _M.new(fraction) 20 | if fraction >= 1 then 21 | return always_on_sampler_new() 22 | end 23 | 24 | if fraction < 0 then 25 | fraction = 0 26 | end 27 | 28 | return setmetatable({ 29 | trace_id_upper_bound = fraction * 0xffffffff, 30 | description = string.format("TraceIDRatioBased{%d}", fraction) 31 | }, mt) 32 | end 33 | 34 | function _M.should_sample(self, parameters) 35 | local parent_span_ctx = parameters.parent_ctx:span_context() 36 | local n = 0 37 | local trace_id = parameters.trace_id 38 | for i = 1, 8, 2 do 39 | n = tonumber(string.sub(trace_id, i, i + 1), 16) + (n * (2 ^ 8)) 40 | end 41 | 42 | if n < self.trace_id_upper_bound then 43 | return result_new(2, parent_span_ctx.trace_state) 44 | end 45 | 46 | return result_new(0, parent_span_ctx.trace_state) 47 | end 48 | 49 | function _M.description(self) 50 | return self.description 51 | end 52 | 53 | return _M 54 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/exporter/http_client.lua: -------------------------------------------------------------------------------- 1 | local http = require("resty.http") 2 | local net_url = require("net.url") 3 | 4 | local _M = { 5 | } 6 | 7 | local mt = { 8 | __index = _M 9 | } 10 | 11 | local function build_uri(address) 12 | local parsed_address = net_url.parse(address) 13 | if parsed_address.scheme ~= "http" and parsed_address.scheme ~= "https" then 14 | return build_uri("http://" .. address) 15 | end 16 | if parsed_address.path == "" or parsed_address.path == "/" then 17 | parsed_address.path = "/v1/traces" 18 | end 19 | return tostring(parsed_address) 20 | end 21 | 22 | ------------------------------------------------------------------ 23 | -- create a http client used by exporter. 24 | -- 25 | -- @address opentelemetry collector: host:port 26 | -- @timeout export request timeout second 27 | -- @headers export request headers 28 | -- @return http client 29 | ------------------------------------------------------------------ 30 | function _M.new(address, timeout, headers) 31 | headers = headers or {} 32 | headers["Content-Type"] = "application/x-protobuf" 33 | 34 | local self = { 35 | uri = build_uri(address), 36 | timeout = timeout, 37 | headers = headers, 38 | } 39 | return setmetatable(self, mt) 40 | end 41 | 42 | function _M.do_request(self, body) 43 | local httpc = http.new() 44 | httpc:set_timeout(self.timeout * 1000) 45 | 46 | local res, err = httpc:request_uri(self.uri, { 47 | method = "POST", 48 | headers = self.headers, 49 | body = body, 50 | }) 51 | 52 | if not res then 53 | ngx.log(ngx.ERR, "request failed: ", err) 54 | httpc:close() 55 | return nil, err 56 | end 57 | 58 | if res.status ~= 200 then 59 | ngx.log(ngx.ERR, "request failed: ", res.body) 60 | httpc:close() 61 | return nil, "request failed: " .. res.status 62 | end 63 | 64 | return res, nil 65 | end 66 | 67 | return _M 68 | -------------------------------------------------------------------------------- /lib/opentelemetry/metrics_reporter.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- This defines an interface used for reporting metrics. It defaults to a noop. 3 | -- Users can supply a module that satisfies this interface to actually generate 4 | -- metrics. 5 | -------------------------------------------------------------------------------- 6 | 7 | local _M = {} 8 | 9 | -------------------------------------------------------------------------------- 10 | -- Adds an increment to a metric with the provided labels. This should be used 11 | -- with counter metrics 12 | -- 13 | -- @param metric The metric to increment 14 | -- @param increment The amount to increment the metric by 15 | -- @param labels The labels to use for the metric 16 | -- return nil 17 | -------------------------------------------------------------------------------- 18 | function _M:add_to_counter(metric, increment, labels) 19 | return nil 20 | end 21 | 22 | -------------------------------------------------------------------------------- 23 | -- Record a value for metric with provided labels. This should be used with 24 | -- histogram or distribution metrics. 25 | -- 26 | -- @param metric The metric to record a value for 27 | -- @param value The value to set for the metric 28 | -- @param labels The labels to use for the metric 29 | -- return nil 30 | -------------------------------------------------------------------------------- 31 | function _M:record_value(metric, value, labels) 32 | return nil 33 | end 34 | 35 | -------------------------------------------------------------------------------- 36 | -- Observe a value for a metric with provided labels. This corresponds to the 37 | -- gauge metric type in datadog 38 | -- 39 | -- @param metric The metric to record a value for 40 | -- @param value The value to set for the metric 41 | -- @param labels The labels to use for the metric 42 | -- return nil 43 | -------------------------------------------------------------------------------- 44 | function _M:observe_value(metric, value, labels) 45 | return nil 46 | end 47 | 48 | return _M 49 | -------------------------------------------------------------------------------- /spec/trace/exporter/circuit_spec.lua: -------------------------------------------------------------------------------- 1 | local util = require("lib.opentelemetry.util") 2 | local circuit = require("opentelemetry.trace.exporter.circuit") 3 | 4 | describe("should_make_request", function() 5 | it("returns true when circuit is closed", function() 6 | local c = circuit.new() 7 | c.state = c.CLOSED 8 | assert.is_true(c:should_make_request()) 9 | end) 10 | 11 | it("returns false when circuit is open and halfopen_threshold not exceeded", function() 12 | local c = circuit.new({ reset_timeout_ms = 5000 }) 13 | c.state = c.OPEN 14 | c.open_start_time_ms = util.gettimeofday_ms() 15 | assert.is_false(c:should_make_request()) 16 | end) 17 | 18 | it("returns true when circuit is open and halfopen_threshold is exceeded", function() 19 | local c = circuit.new({ reset_timeout_ms = 5000 }) 20 | c.state = c.OPEN 21 | c.open_start_time_ms = util.gettimeofday_ms() - 6000 22 | assert.is_true(c:should_make_request()) 23 | end) 24 | end) 25 | 26 | describe("record_failure", function() 27 | it("opens circuit if failure count > self.failure_threshold", function() 28 | local c = circuit.new({ failure_threshold = 1 }) 29 | assert.is_equal(c.state, c.CLOSED) 30 | assert.is_equal(c.open_start_time_ms, nil) 31 | c:record_failure() 32 | assert.is_equal(c.state, c.OPEN) 33 | assert.are_not.equals(c.open_start_time_ms, nil) 34 | end) 35 | 36 | it("opens circuit if half-open on entry", function() 37 | local c = circuit.new({ failure_threshold = 5 }) 38 | c.state = c.HALF_OPEN 39 | c:record_failure() 40 | assert.is_equal(c.state, c.OPEN) 41 | assert.are_not.equals(c.open_start_time_ms, nil) 42 | end) 43 | end) 44 | 45 | describe("record_success", function() 46 | it("closes circuit if circuit was half-open", function() 47 | local c = circuit.new({ failure_threshold = 1 }) 48 | c.state = c.HALF_OPEN 49 | c:record_success() 50 | assert.is_equal(c.state, c.CLOSED) 51 | end) 52 | end) 53 | -------------------------------------------------------------------------------- /lib/opentelemetry/semantic_conventions/trace/rpc.lua: -------------------------------------------------------------------------------- 1 | --- This file was automatically generated by utils/generate_semantic_conventions.lua 2 | -- See: https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions 3 | -- 4 | -- module @semantic_conventions.trace.rpc 5 | local _M = { 6 | -- A string identifying the remoting system. See below for a list of well-known identifiers. 7 | RPC_SYSTEM = "rpc.system", 8 | -- The full (logical) name of the service being called, including its package name, if applicable. 9 | RPC_SERVICE = "rpc.service", 10 | -- The name of the (logical) method being called, must be equal to the $method part in the span name. 11 | RPC_METHOD = "rpc.method", 12 | -- The [numeric status code](https://github.com/grpc/grpc/blob/v1.33.2/doc/statuscodes.md) of the gRPC request. 13 | RPC_GRPC_STATUS_CODE = "rpc.grpc.status_code", 14 | -- Protocol version as in `jsonrpc` property of request/response. Since JSON-RPC 1.0 does not specify this, the value can be omitted. 15 | RPC_JSONRPC_VERSION = "rpc.jsonrpc.version", 16 | -- `id` property of request or response. Since protocol allows id to be int, string, `null` or missing (for notifications), value is expected to be cast to string for simplicity. Use empty string in case of `null` value. Omit entirely if this is a notification. 17 | RPC_JSONRPC_REQUEST_ID = "rpc.jsonrpc.request_id", 18 | -- `error.code` property of response if it is an error response. 19 | RPC_JSONRPC_ERROR_CODE = "rpc.jsonrpc.error_code", 20 | -- `error.message` property of response if it is an error response. 21 | RPC_JSONRPC_ERROR_MESSAGE = "rpc.jsonrpc.error_message", 22 | -- Whether this is a received or sent message. 23 | MESSAGE_TYPE = "message.type", 24 | -- MUST be calculated as two different counters starting from `1` one for sent messages and one for received message. 25 | MESSAGE_ID = "message.id", 26 | -- Compressed size of the message in bytes. 27 | MESSAGE_COMPRESSED_SIZE = "message.compressed_size", 28 | -- Uncompressed size of the message in bytes. 29 | MESSAGE_UNCOMPRESSED_SIZE = "message.uncompressed_size" 30 | } 31 | return _M 32 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/propagation/text_map/composite_propagator.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- The composite propagator bundles together multiple propagators and executes 3 | -- them in sequence. 4 | -- See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/api-propagators.md#composite-propagator 5 | -------------------------------------------------------------------------------- 6 | 7 | local _M = { 8 | } 9 | 10 | local mt = { 11 | __index = _M 12 | } 13 | 14 | -------------------------------------------------------------------------------- 15 | -- Returns a new composite propagator. Propagators must adhere to the API 16 | -- defined in the spec 17 | -- See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/api-propagators.md 18 | -- 19 | -- @param propagators 20 | -- @return a new composite propagator 21 | -------------------------------------------------------------------------------- 22 | function _M.new(propagators) 23 | return setmetatable({ propagators = propagators }, mt) 24 | end 25 | 26 | -------------------------------------------------------------------------------- 27 | -- Uses the propagators to inject context into carrier in sequence. 28 | -- 29 | -- @param context context module 30 | -- @param carrier carrier (e.g. ngx.req) 31 | -------------------------------------------------------------------------------- 32 | function _M:inject(context, carrier) 33 | for i = 1, #self.propagators do 34 | self.propagators[i]:inject(context, carrier) 35 | end 36 | end 37 | 38 | -------------------------------------------------------------------------------- 39 | -- Uses the propagators to extract context into carrier in sequence. 40 | -- 41 | -- @param context context module 42 | -- @param carrier carrier (e.g. ngx.req) 43 | -------------------------------------------------------------------------------- 44 | function _M:extract(context, carrier) 45 | local new_ctx = context 46 | for i = 1, #self.propagators do 47 | new_ctx = self.propagators[i]:extract(new_ctx, carrier) 48 | end 49 | return new_ctx 50 | end 51 | 52 | return _M 53 | -------------------------------------------------------------------------------- /lib/opentelemetry/attribute.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | 3 | function _M.string(key, value) 4 | return { 5 | key = key, 6 | value = { 7 | string_value = value, 8 | } 9 | } 10 | end 11 | 12 | function _M.string_array(key, values) 13 | local ret = { 14 | key = key, 15 | value = { 16 | array_value = { 17 | values = {} 18 | } 19 | } 20 | } 21 | for i = 1, #values do 22 | table.insert(ret.value.array_value.values, {string_value = values[i]}) 23 | end 24 | return ret 25 | end 26 | 27 | function _M.int(key, value) 28 | return { 29 | key = key, 30 | value = { 31 | int_value = value, 32 | } 33 | } 34 | end 35 | 36 | function _M.int_array(key, values) 37 | local ret = { 38 | key = key, 39 | value = { 40 | array_value = { 41 | values = {} 42 | } 43 | } 44 | } 45 | for i = 1, #values do 46 | table.insert(ret.value.array_value.values, {int_value = values[i]}) 47 | end 48 | return ret 49 | end 50 | 51 | function _M.bool(key, value) 52 | return { 53 | key = key, 54 | value = { 55 | bool_value = value, 56 | } 57 | } 58 | end 59 | 60 | function _M.bool_array(key, values) 61 | local ret = { 62 | key = key, 63 | value = { 64 | array_value = { 65 | values = {} 66 | } 67 | } 68 | } 69 | for i = 1, #values do 70 | table.insert(ret.value.array_value.values, {bool_value = values[i]}) 71 | end 72 | return ret 73 | end 74 | 75 | function _M.double(key, value) 76 | return { 77 | key = key, 78 | value = { 79 | double_value = value, 80 | } 81 | } 82 | end 83 | 84 | function _M.double_array(key, values) 85 | local ret = { 86 | key = key, 87 | value = { 88 | array_value = { 89 | values = {} 90 | } 91 | } 92 | } 93 | for i = 1, #values do 94 | table.insert(ret.value.array_value.values, {double_value = values[i]}) 95 | end 96 | return ret 97 | end 98 | 99 | return _M -------------------------------------------------------------------------------- /lib/opentelemetry/semantic_conventions/trace/faas.lua: -------------------------------------------------------------------------------- 1 | --- This file was automatically generated by utils/generate_semantic_conventions.lua 2 | -- See: https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions 3 | -- 4 | -- module @semantic_conventions.trace.faas 5 | local _M = { 6 | -- Type of the trigger which caused this function execution. 7 | FAAS_TRIGGER = "faas.trigger", 8 | -- The execution ID of the current function execution. 9 | FAAS_EXECUTION = "faas.execution", 10 | -- The name of the source on which the triggering operation was performed. For example, in Cloud Storage or S3 corresponds to the bucket name, and in Cosmos DB to the database name. 11 | FAAS_DOCUMENT_COLLECTION = "faas.document.collection", 12 | -- Describes the type of the operation that was performed on the data. 13 | FAAS_DOCUMENT_OPERATION = "faas.document.operation", 14 | -- A string containing the time when the data was accessed in the [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format expressed in [UTC](https://www.w3.org/TR/NOTE-datetime). 15 | FAAS_DOCUMENT_TIME = "faas.document.time", 16 | -- The document name/table subjected to the operation. For example, in Cloud Storage or S3 is the name of the file, and in Cosmos DB the table name. 17 | FAAS_DOCUMENT_NAME = "faas.document.name", 18 | -- A string containing the function invocation time in the [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format expressed in [UTC](https://www.w3.org/TR/NOTE-datetime). 19 | FAAS_TIME = "faas.time", 20 | -- A string containing the schedule period as [Cron Expression](https://docs.oracle.com/cd/E12058_01/doc/doc.1014/e12030/cron_expressions.htm). 21 | FAAS_CRON = "faas.cron", 22 | -- A boolean that is true if the serverless function is executed for the first time (aka cold-start). 23 | FAAS_COLDSTART = "faas.coldstart", 24 | -- The name of the invoked function. 25 | FAAS_INVOKED_NAME = "faas.invoked_name", 26 | -- The cloud provider of the invoked function. 27 | FAAS_INVOKED_PROVIDER = "faas.invoked_provider", 28 | -- The cloud region of the invoked function. 29 | FAAS_INVOKED_REGION = "faas.invoked_region" 30 | } 31 | return _M 32 | -------------------------------------------------------------------------------- /t/trace/sampling/trace_id_ratio_sampler.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | log_level('debug'); 4 | repeat_each(1); 5 | no_long_string(); 6 | no_root_location(); 7 | run_tests(); 8 | 9 | __DATA__ 10 | 11 | === TEST 1: trace_id_ratio_sampler:should_sample 12 | --- config 13 | location = /t { 14 | content_by_lua_block { 15 | local trace_id_ratio_sampler_new = require("opentelemetry.trace.sampling.trace_id_ratio_sampler").new 16 | local context_new = require("opentelemetry.context").new 17 | 18 | ngx.say("test fraction = 0") 19 | local result = trace_id_ratio_sampler_new(0):should_sample({ 20 | parent_ctx = context_new(), 21 | trace_id = "00000000000000000000000000000000", 22 | }) 23 | if result:is_sampled() then 24 | ngx.say("expect result:is_sampled() == false") 25 | return 26 | end 27 | 28 | ngx.say("test fraction = 1") 29 | local result = trace_id_ratio_sampler_new(1):should_sample({ 30 | parent_ctx = context_new(), 31 | trace_id = "ffffffff000000000000000000000000", 32 | }) 33 | if not result:is_sampled() then 34 | ngx.say("expect result:is_sampled() == true") 35 | return 36 | end 37 | 38 | ngx.say("test fraction = 0.5") 39 | local result = trace_id_ratio_sampler_new(0.5):should_sample({ 40 | parent_ctx = context_new(), 41 | trace_id = "7fffffff000000000000000000000000", 42 | }) 43 | if not result:is_sampled() then 44 | ngx.say("expect result:is_sampled() == true") 45 | return 46 | end 47 | 48 | ngx.say("test fraction = 0.5") 49 | local result = trace_id_ratio_sampler_new(0.5):should_sample({ 50 | parent_ctx = context_new(), 51 | trace_id = "80000000000000000000000000000000", 52 | }) 53 | if result:is_sampled() then 54 | ngx.say("expect result:is_sampled() == false") 55 | return 56 | end 57 | 58 | ngx.say("done") 59 | } 60 | } 61 | --- request 62 | GET /t 63 | --- error_code: 200 64 | --- response_body 65 | test fraction = 0 66 | test fraction = 1 67 | test fraction = 0.5 68 | test fraction = 0.5 69 | done 70 | --- no_error_log 71 | [error] 72 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: doc format api-test benchmark 2 | CONTAINER_ORCHESTRATOR ?= docker-compose 3 | CONTAINER_ORCHESTRATOR_EXEC_OPTIONS := $(CONTAINER_ORCHESTRATOR_EXEC_OPTIONS) 4 | 5 | openresty-dev: 6 | $(CONTAINER_ORCHESTRATOR) up -d openresty 7 | $(CONTAINER_ORCHESTRATOR) exec $(CONTAINER_ORCHESTRATOR_EXEC_OPTIONS) -- openresty bash -c 'luarocks make && nginx -s reload' 8 | 9 | openresty-test-e2e: 10 | $(CONTAINER_ORCHESTRATOR) run -e PROXY_ENDPOINT=http://openresty/test/e2e --rm test-client 11 | 12 | openresty-test-e2e-trace-context: 13 | $(CONTAINER_ORCHESTRATOR) exec $(CONTAINER_ORCHESTRATOR_EXEC_OPTIONS) -- openresty bash /opt/opentelemetry-lua/e2e-trace-context.sh 14 | 15 | openresty-integration-test: 16 | $(CONTAINER_ORCHESTRATOR) exec $(CONTAINER_ORCHESTRATOR_EXEC_OPTIONS) -- openresty bash -c 'cd /opt/opentelemetry-lua && prove -r' 17 | 18 | lua-unit-test: 19 | $(CONTAINER_ORCHESTRATOR) run --no-deps $(CONTAINER_ORCHESTRATOR_EXEC_OPTIONS) -- openresty bash -c './busted-runner' 20 | 21 | openresty-build: 22 | $(CONTAINER_ORCHESTRATOR) build 23 | 24 | doc: 25 | $(CONTAINER_ORCHESTRATOR) run $(CONTAINER_ORCHESTRATOR_EXEC_OPTIONS) -- openresty bash -c 'ldoc lib/opentelemetry/api' 26 | 27 | check-format: 28 | $(CONTAINER_ORCHESTRATOR) run --no-deps $(CONTAINER_ORCHESTRATOR_EXEC_OPTIONS) -- utils bash -c 'lua-format --check lib/opentelemetry/api/**/*.lua spec/api/**/*.lua' 29 | 30 | format: 31 | $(CONTAINER_ORCHESTRATOR) run --no-deps $(CONTAINER_ORCHESTRATOR_EXEC_OPTIONS) -- utils bash -c 'lua-format -i lib/opentelemetry/api/**/*.lua spec/api/**/*.lua' 32 | 33 | api-test: 34 | $(CONTAINER_ORCHESTRATOR) run --no-deps $(CONTAINER_ORCHESTRATOR_EXEC_OPTIONS) -- openresty bash -c 'busted -m "./lib/?.lua;./lib/?/?.lua;./lib/?/?/?.lua" ./spec/api' 35 | 36 | generate-semantic-conventions: 37 | $(CONTAINER_ORCHESTRATOR) run --no-deps $(CONTAINER_ORCHESTRATOR_EXEC_OPTIONS) -- openresty bash -c 'pushd tmp && rm -rf opentelemetry-specification && git clone --depth=1 https://github.com/open-telemetry/opentelemetry-specification.git && popd && resty ./utils/generate_semantic_conventions.lua && lua-format -i lib/opentelemetry/semantic_conventions/trace/*.lua' 38 | 39 | benchmark: 40 | $(CONTAINER_ORCHESTRATOR) run --no-deps $(CONTAINER_ORCHESTRATOR_EXEC_OPTIONS) -- openresty bash -c 'cd /opt/opentelemetry-lua && benchmark/run.sh' 41 | -------------------------------------------------------------------------------- /spec/trace/tracestate_spec.lua: -------------------------------------------------------------------------------- 1 | local tracestate = require("opentelemetry.trace.tracestate") 2 | 3 | describe("is_valid", function() 4 | it("parse, get works", function() 5 | local ts = tracestate.parse_tracestate("foo=bar,baz=lehrman") 6 | assert.is_true(#ts.values == 2) 7 | assert.is_true(ts:get("foo") == "bar") 8 | assert.is_true(ts:get("baz") == "lehrman") 9 | end) 10 | it("set works", function() 11 | local ts = tracestate.parse_tracestate("foo=bar,baz=lehrman") 12 | assert.is_true(#ts.values == 2) 13 | ts:set("foo", "fun") 14 | assert.is_true(#ts.values == 2) 15 | assert.is_true(ts:get("foo") == "fun") 16 | ts:set("family", "values") 17 | assert.is_true(#ts.values == 3) 18 | assert.is_true(ts:get("family") == "values") 19 | -- setting an invalid value leaves the old kv pair 20 | ts:set("foo", "v=l") 21 | assert.is_true(ts:get("foo") == "fun") 22 | end) 23 | it("del works", function() 24 | local ts = tracestate.parse_tracestate("foo=bar,baz=lehrman") 25 | ts:del("foo") 26 | assert.is_true(#ts.values == 1) 27 | assert.is_true(ts:get("foo") == "") 28 | end) 29 | it("as_string works", function() 30 | local ts = tracestate.parse_tracestate("foo=bar,baz=lehrman") 31 | assert.is_true(ts:as_string() == "foo=bar,baz=lehrman") 32 | ts:set("bing", "bong") 33 | assert.is_true(ts:as_string() == "bing=bong,foo=bar,baz=lehrman") 34 | end) 35 | it("max len is respected", function() 36 | -- Supress logs during test, since we expect them. 37 | stub(ngx, "log") 38 | local ts = tracestate.parse_tracestate("") 39 | for i=1,tracestate.MAX_ENTRIES,1 do 40 | ts:set("a" .. tostring(i), "b" .. tostring(i)) 41 | end 42 | assert.is_true(#ts.values == tracestate.MAX_ENTRIES) 43 | ts:set("one", "more") 44 | ngx.log:revert() 45 | assert.is_true(#ts.values == tracestate.MAX_ENTRIES) 46 | -- First elem added is the first one lost when we add over max entries 47 | assert.is_true(ts:get("a1") == "") 48 | assert.is_true(ts:get("one") == "more") 49 | -- Newest elem is prepended 50 | assert.is_true(ts.values[1][1] == "one") 51 | end) 52 | end) 53 | -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 42 | 43 |
44 | 45 |

Module api.trace.span_status

46 |

Span status represents the status of a span.

47 |

Like an HTTP code, but for your span. It is either unset, ok, or error. 48 |

49 | 50 | 51 |

Functions

52 | 53 | 54 | 55 | 56 | 57 |
new (code)Returns a new span_status.
58 | 59 |
60 |
61 | 62 | 63 |

Functions

64 | 65 |
66 |
67 | 68 | new (code) 69 |
70 |
71 | Returns a new span_status. 72 | 73 | 74 |

Parameters:

75 |
    76 |
  • code 77 | int 78 | The status code. Defaults to UNSET. 79 |
  • 80 |
81 | 82 | 83 | 84 | 85 | 86 |
87 |
88 | 89 | 90 |
91 |
92 |
93 | generated by LDoc 1.4.6 94 | Last updated 2023-01-18 23:39:56 95 |
96 |
97 | 98 | 99 | -------------------------------------------------------------------------------- /t/trace/tracer.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | log_level('debug'); 4 | repeat_each(1); 5 | no_long_string(); 6 | no_root_location(); 7 | run_tests(); 8 | 9 | __DATA__ 10 | 11 | === TEST 1: tracer:start 12 | --- config 13 | location = /t { 14 | content_by_lua_block { 15 | local tracer_provider = require("opentelemetry.trace.tracer_provider").new() 16 | local context = require("opentelemetry.context").new() 17 | local span_context_new = require("opentelemetry.trace.span_context").new 18 | local span_kind = require("opentelemetry.trace.span_kind") 19 | local attr = require("opentelemetry.attribute") 20 | local tracer = tracer_provider:tracer("unit_test") 21 | 22 | ngx.say("test start recording_span") 23 | local context, recording_span = tracer:start(context, "recording", 24 | {kind = span_kind.producer, attributes = {attr.string("key", "value")}}) 25 | if not (recording_span.name == "recording" and recording_span.start_time > 0 26 | and recording_span.kind == span_kind.producer 27 | and #recording_span.attributes == 1 28 | and recording_span.attributes[1].key == "key" 29 | and recording_span.attributes[1].value.string_value == "value") then 30 | ngx.say("unexpected recording_span") 31 | end 32 | if not recording_span:is_recording() then 33 | ngx.say("expect recording") 34 | end 35 | 36 | ngx.say("test start non_recording_span") 37 | local always_off_sampler = require("opentelemetry.trace.sampling.always_off_sampler").new() 38 | tracer_provider.sampler = always_off_sampler 39 | local context, non_recording_span = tracer:start(context, "non_recording", 40 | {kind = span_kind.consumer, attributes = {attr.string("key", "value")}}) 41 | if not (non_recording_span.name == nil and non_recording_span.start_time == nil 42 | and non_recording_span.kind == nil 43 | and non_recording_span.attributes == nil) then 44 | ngx.say("unexpected non_recording_span") 45 | end 46 | if non_recording_span:is_recording() then 47 | ngx.say("expect non_recording") 48 | end 49 | 50 | ngx.say("done") 51 | } 52 | } 53 | --- request 54 | GET /t 55 | --- error_code: 200 56 | --- response_body 57 | test start recording_span 58 | test start non_recording_span 59 | done 60 | --- no_error_log 61 | [error] -------------------------------------------------------------------------------- /t/context.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | log_level('debug'); 4 | repeat_each(1); 5 | no_long_string(); 6 | no_root_location(); 7 | run_tests(); 8 | 9 | __DATA__ 10 | 11 | === TEST 1: context 12 | --- config 13 | location = /t { 14 | content_by_lua_block { 15 | local span_context_new = require("opentelemetry.trace.span_context").new 16 | local context = require("opentelemetry.context").new() 17 | 18 | ngx.say("text context:with_span_context") 19 | context = context:with_span_context(span_context_new("trace_id", "span_id", 1, "trace_state", false)) 20 | local span_context = context:span_context() 21 | if not (span_context.trace_id == "trace_id" and span_context.span_id == "span_id" and 22 | span_context.trace_flags == 1 and span_context.trace_state == "trace_state" and 23 | span_context.remote == false) then 24 | ngx.say("unexpected span_context") 25 | end 26 | if context:span():is_recording() then 27 | ngx.say("unexpected span") 28 | end 29 | 30 | ngx.say("test context attach & detach") 31 | local tracer_provider = require("opentelemetry.trace.tracer_provider").new() 32 | local tracer = tracer_provider:tracer("unit_test") 33 | local context, recording_span = tracer:start(context, "recording") 34 | if context.current().sp:is_recording() ~= false then 35 | ngx.say("expected context.current() span to be non-recording") 36 | end 37 | context:attach() 38 | if context.current():span().name ~= "recording" then 39 | ngx.say("unexpected context.current():span()") 40 | end 41 | context, recording_span = tracer:start(context, "recording2") 42 | context:attach() 43 | if context.current():span().name ~= "recording2" then 44 | ngx.say("unexpected context.current():span()") 45 | end 46 | context:detach(2) 47 | if context.current():span().name ~= "recording" then 48 | ngx.say("unexpected context.current():span()") 49 | end 50 | context.current():detach(1) 51 | if context.current() ~= nil then 52 | ngx.say("unexpected context.current()") 53 | end 54 | ngx.say("done") 55 | } 56 | } 57 | --- request 58 | GET /t 59 | --- error_code: 200 60 | --- response_body 61 | text context:with_span_context 62 | test context attach & detach 63 | done 64 | --- no_error_log 65 | [error] 66 | -------------------------------------------------------------------------------- /doc/modules/api.trace.span_status.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 46 | 47 |
48 | 49 |

Module api.trace.span_status

50 |

Span status represents the status of a span.

51 |

Like an HTTP code, but for your span. It is either unset, ok, or error. 52 |

53 | 54 | 55 |

Functions

56 | 57 | 58 | 59 | 60 | 61 |
new (code)Returns a new span_status.
62 | 63 |
64 |
65 | 66 | 67 |

Functions

68 | 69 |
70 |
71 | 72 | new (code) 73 |
74 |
75 | Returns a new span_status. 76 | 77 | 78 |

Parameters:

79 |
    80 |
  • code 81 | int 82 | The status code. Defaults to UNSET. 83 |
  • 84 |
85 | 86 | 87 | 88 | 89 | 90 |
91 |
92 | 93 | 94 |
95 |
96 |
97 | generated by LDoc 1.4.6 98 | Last updated 2023-01-18 22:27:32 99 |
100 |
101 | 102 | 103 | -------------------------------------------------------------------------------- /lib/opentelemetry/baggage.lua: -------------------------------------------------------------------------------- 1 | local util = require("opentelemetry.util") 2 | 3 | local _M = { 4 | } 5 | 6 | local mt = { 7 | __index = _M 8 | } 9 | 10 | function _M.new(values) 11 | return setmetatable({ values = values or {} }, mt) 12 | end 13 | 14 | -------------------------------------------------------------------------------- 15 | -- Set a value in a baggage instance. Does _not_ inject into context 16 | -- 17 | -- @name name for which to set the value in baggage 18 | -- @value value to set must be string 19 | -- @metadata metadata to set in baggage (string) 20 | -- @return baggage 21 | -------------------------------------------------------------------------------- 22 | function _M.set_value(self, name, value, metadata) 23 | local new_values = util.shallow_copy_table(self.values) 24 | new_values[name] = { value = value, metadata = metadata } 25 | return self.new(new_values) 26 | end 27 | 28 | -------------------------------------------------------------------------------- 29 | -- Get value stored at a specific name in a baggage instance 30 | -- 31 | -- @name name for which to set the value in baggage 32 | -- @return baggage 33 | -------------------------------------------------------------------------------- 34 | function _M.get_value(self, name) 35 | if self.values[name] then 36 | return self.values[name].value 37 | else 38 | return nil 39 | end 40 | end 41 | 42 | -------------------------------------------------------------------------------- 43 | -- Remove value stored at a specific name in a baggage instance. 44 | -- 45 | -- @name name to remove from baggage 46 | -- @return baggage 47 | -------------------------------------------------------------------------------- 48 | function _M.remove_value(self, name) 49 | local new_values = util.shallow_copy_table(self.values) 50 | new_values[name] = nil 51 | return self.new(new_values) 52 | end 53 | 54 | -------------------------------------------------------------------------------- 55 | -- Get all values in a baggage instance. This is supposed to return an immutable 56 | -- collection, but we just return a copy of the table stored at values. 57 | -- 58 | -- @context context from which to access the baggage (defaults to 59 | -- current context) 60 | -- @return table like { keyname = { value = "value", 61 | -- metadata = "metadatastring"} } 62 | -------------------------------------------------------------------------------- 63 | function _M.get_all_values(self) 64 | return util.shallow_copy_table(self.values) 65 | end 66 | 67 | return _M 68 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/tracer.lua: -------------------------------------------------------------------------------- 1 | local bit = require("bit") 2 | local span_context_new = require("opentelemetry.trace.span_context").new 3 | local non_recording_span_new = require("opentelemetry.trace.non_recording_span").new 4 | local recording_span_new = require("opentelemetry.trace.recording_span").new 5 | local span_kind = require("opentelemetry.trace.span_kind") 6 | 7 | local _M = { 8 | } 9 | 10 | local mt = { 11 | __index = _M 12 | } 13 | 14 | function _M.new(provider, il) 15 | local self = { 16 | provider = provider, 17 | il = il 18 | } 19 | return setmetatable(self, mt) 20 | end 21 | 22 | local function new_span(self, context, name, config, start_time) 23 | local span_context = context:span_context() 24 | if not config then 25 | config = {} 26 | end 27 | local trace_id = span_context.trace_id 28 | local span_id 29 | if trace_id then 30 | span_id = self.provider.id_generator.new_span_id(trace_id) 31 | else 32 | trace_id, span_id = self.provider.id_generator.new_ids() 33 | end 34 | 35 | local sampling_result = self.provider.sampler:should_sample({ 36 | parent_ctx = context, 37 | trace_id = trace_id, 38 | name = name, 39 | kind = span_kind.validate(config.kind), 40 | attributes = config.attributes, 41 | }) 42 | 43 | local trace_flags = span_context.trace_flags and span_context.trace_flags or 0 44 | if sampling_result:is_sampled() then 45 | trace_flags = bit.bor(trace_flags, 1) 46 | else 47 | trace_flags = bit.band(trace_flags, 0) 48 | end 49 | local new_span_context = span_context_new(trace_id, span_id, trace_flags, sampling_result.trace_state, false) 50 | 51 | local span 52 | if not sampling_result:is_recording() then 53 | span = non_recording_span_new(self, new_span_context) 54 | else 55 | span = recording_span_new(self, span_context, new_span_context, name, config, start_time) 56 | end 57 | 58 | return context:with_span(span), span 59 | end 60 | 61 | ------------------------------------------------------------------ 62 | -- create a span. 63 | -- 64 | -- @context context with parent span 65 | -- @span_name span name 66 | -- @span_start_config [optional] 67 | -- span_start_config.kind: opentelemetry.trace.span_kind.* 68 | -- span_start_config.attributes: a list of attribute 69 | -- @start_time [optional] start time: nanoseconds 70 | -- @return 71 | -- context: new context with span 72 | -- span 73 | ------------------------------------------------------------------ 74 | function _M.start(self, context, span_name, span_start_config, start_time) 75 | return new_span(self, context, span_name, span_start_config, start_time) 76 | end 77 | 78 | return _M 79 | -------------------------------------------------------------------------------- /t/trace/sampling/parent_base_sampler.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | log_level('debug'); 4 | repeat_each(1); 5 | no_long_string(); 6 | no_root_location(); 7 | run_tests(); 8 | 9 | __DATA__ 10 | 11 | === TEST 1: parent_base_sampler:should_sample 12 | --- config 13 | location = /t { 14 | content_by_lua_block { 15 | local always_on_sampler = require("opentelemetry.trace.sampling.always_on_sampler").new() 16 | local parent_base_sampler = require("opentelemetry.trace.sampling.parent_base_sampler").new(always_on_sampler) 17 | local span_context_new = require("opentelemetry.trace.span_context").new 18 | local context_new = require("opentelemetry.context").new 19 | 20 | ngx.say("test invalid parameters.parent_ctx") 21 | local result = parent_base_sampler:should_sample({ 22 | parent_ctx = context_new() 23 | }) 24 | if not result:is_sampled() then 25 | ngx.say("expect result:is_sampled() == true") 26 | return 27 | end 28 | 29 | ngx.say("test parameters.parent_ctx{remote = true}") 30 | local result = parent_base_sampler:should_sample({ 31 | parent_ctx = context_new():with_span_context(span_context_new("trace_id", "span_id", 0, "trace_state", true)) 32 | }) 33 | if result:is_sampled() then 34 | ngx.say("expect result:is_sampled() == false") 35 | return 36 | end 37 | 38 | ngx.say("test parameters.parent_ctx{remote = true, sampled = true}") 39 | local result = parent_base_sampler:should_sample({ 40 | parent_ctx = context_new():with_span_context(span_context_new("trace_id", "span_id", 1, "trace_state", true)) 41 | }) 42 | if not result:is_sampled() then 43 | ngx.say("expect result:is_sampled() == true") 44 | return 45 | end 46 | 47 | ngx.say("test parameters.parent_ctx{remote = false, sampled = true}") 48 | local result = parent_base_sampler:should_sample({ 49 | parent_ctx = context_new():with_span_context(span_context_new("trace_id", "span_id", 1, "trace_state", true)) 50 | }) 51 | if not result:is_sampled() then 52 | ngx.say("expect result:is_sampled() == true") 53 | return 54 | end 55 | 56 | ngx.say("test parameters.parent_ctx{remote = false, sampled = false}") 57 | local result = parent_base_sampler:should_sample({ 58 | parent_ctx = context_new():with_span_context(span_context_new("trace_id", "span_id", 0, "trace_state", false)) 59 | }) 60 | if result:is_sampled() then 61 | ngx.say("expect result:is_sampled() == false") 62 | return 63 | end 64 | 65 | ngx.say("done") 66 | } 67 | } 68 | --- request 69 | GET /t 70 | --- error_code: 200 71 | --- response_body 72 | test invalid parameters.parent_ctx 73 | test parameters.parent_ctx{remote = true} 74 | test parameters.parent_ctx{remote = true, sampled = true} 75 | test parameters.parent_ctx{remote = false, sampled = true} 76 | test parameters.parent_ctx{remote = false, sampled = false} 77 | done 78 | --- no_error_log 79 | [error] 80 | -------------------------------------------------------------------------------- /utils/generate_semantic_conventions.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------------------------------------------------ 2 | -- This script grabs semantic conventions from the opentelemetry specification repository and generates Lua modules 3 | -- for each file. 4 | ------------------------------------------------------------------------------------------------------------------------ 5 | 6 | local pl_dir = require('pl.dir') 7 | local pl_file = require('pl.file') 8 | local files_by_dir = {} 9 | local dirs = pl_dir.getdirectories("tmp/opentelemetry-specification/semantic_conventions/trace/") 10 | 11 | for _i, dir in ipairs(dirs) do 12 | print(dir) 13 | table.insert(files_by_dir, pl_dir.getfiles(dir, "*.yaml")) 14 | end 15 | 16 | table.insert( 17 | files_by_dir, pl_dir.getfiles("tmp/opentelemetry-specification/semantic_conventions/trace/", "*.yaml") 18 | ) 19 | 20 | local lyaml = require("lyaml") 21 | 22 | for _i, dir in ipairs(files_by_dir) do 23 | for _i, file in ipairs(dir) do 24 | print(file) 25 | local filename = string.match(file, "tmp/opentelemetry%-specification/semantic_conventions/trace/(%a+)") 26 | local outfile = "lib/opentelemetry/semantic_conventions/trace/" .. filename .. ".lua" 27 | local f = io.open(file, "rb") 28 | local content = f:read("*all") 29 | f:close() 30 | local data = lyaml.load(content) 31 | print("Writing semantic conventions to " .. outfile) 32 | local out_str = "--- This file was automatically generated by utils/generate_semantic_conventions.lua\n" 33 | out_str = out_str .. "-- See: https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions\n" 34 | out_str = out_str .. "--\n" 35 | out_str = out_str .. "-- module @semantic_conventions.trace." .. filename .. "\n\n" 36 | out_str = out_str .. "local _M = {" 37 | 38 | local attr_string = "" 39 | for _i, group in ipairs(data.groups) do 40 | local prefix = group.prefix or "" 41 | local prop_prefix = string.upper(string.gsub(prefix, "%.", "_")) 42 | if group.attributes then 43 | for _i, attr in ipairs(group.attributes) do 44 | if attr.id then 45 | local brief = string.gsub((attr.brief or attr.id), "\n", "") 46 | local attr_id_prop = string.upper(string.gsub(attr.id, "%.", "_")) 47 | local attr_id = attr.id 48 | if prefix then 49 | attr_id_prop = prop_prefix .. "_" .. attr_id_prop 50 | attr_id = prefix .. "." .. attr_id 51 | end 52 | local new_attr = "\n-- " .. brief .. "\n" .. attr_id_prop .. " = " .. "\"" .. attr_id .. "\"," 53 | attr_string = attr_string .. new_attr 54 | end 55 | end 56 | end 57 | end 58 | pl_file.write(outfile, out_str .. attr_string .. "\n}\n return _M") 59 | end 60 | end 61 | 62 | print("Finished writing semantic conventions. Run `git diff` to see any changes.") 63 | -------------------------------------------------------------------------------- /spec/trace/propagation/text_map/trace_context_propagator_spec.lua: -------------------------------------------------------------------------------- 1 | local text_map_propagator = require "opentelemetry.trace.propagation.text_map.trace_context_propagator" 2 | local tracer_provider = require "opentelemetry.trace.tracer_provider" 3 | local context = require("opentelemetry.context") 4 | 5 | -- We're setting these on ngx.req but we aren't running openresty in these 6 | -- tests, so we'll mock that out (ngx.req supports get_headers() and set_header(header_name)) 7 | local function newCarrier(header, header_return) 8 | local r = { headers = {} } 9 | r.headers[header] = header_return 10 | r.get_headers = function() return r.headers end 11 | r.set_header = function(name, val) r.headers[name] = val end 12 | return r 13 | end 14 | 15 | describe("text map propagator", function() 16 | describe(".fields", function() 17 | local tmp = text_map_propagator.new() 18 | it("should return traceparent and traceheader", function() 19 | assert.are.same({ "traceparent", "tracestate" }, tmp.fields()) 20 | end) 21 | end) 22 | 23 | describe(":inject", function() 24 | it("adds traceparent headers to carrier", function() 25 | local tmp = text_map_propagator.new() 26 | local context = context.new() 27 | local carrier = newCarrier("ok", "alright") 28 | local tracer_provider = tracer_provider.new() 29 | local tracer = tracer_provider:tracer("test tracer") 30 | -- start a trace 31 | local new_ctx = tracer:start(context, "span", { kind = 1 }) 32 | 33 | -- figure out what traceheader should look like 34 | local span_context = new_ctx.sp:context() 35 | local traceparent = string.format("00-%s-%s-%02x", 36 | span_context.trace_id, span_context.span_id, span_context.trace_flags) 37 | 38 | -- make sure we set_header in the setter 39 | spy.on(carrier, "set_header") 40 | tmp:inject(new_ctx, carrier) 41 | assert.spy(carrier.set_header).was.called_with("traceparent", traceparent) 42 | end) 43 | end) 44 | 45 | describe(":extract", function() 46 | it("does not override existing context when none is present in traceparent", function() 47 | local tmp = text_map_propagator.new() 48 | local old_ctx = context.new() 49 | local trace_id = "10f5b3bcfe3f0c2c5e1ef150fe0b5872" 50 | local carrier = newCarrier("nottraceparent", "doesn't matter") 51 | 52 | local ctx = tmp:extract(old_ctx, carrier) 53 | assert.are.same(ctx, old_ctx) 54 | end) 55 | 56 | it("sets context when traceparent is valid", function() 57 | local tmp = text_map_propagator.new() 58 | local context = context.new() 59 | local trace_id = "10f5b3bcfe3f0c2c5e1ef150fe0b5872" 60 | local carrier = newCarrier("traceparent", string.format("00-%s-172accbce5f048db-01", trace_id)) 61 | 62 | local ctx = tmp:extract(context, carrier) 63 | assert.are.same(ctx.sp:context().trace_id, trace_id) 64 | end) 65 | end) 66 | end) 67 | -------------------------------------------------------------------------------- /examples/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | "go.opentelemetry.io/otel" 10 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" 11 | "go.opentelemetry.io/otel/propagation" 12 | "go.opentelemetry.io/otel/sdk/resource" 13 | sdktrace "go.opentelemetry.io/otel/sdk/trace" 14 | semconv "go.opentelemetry.io/otel/semconv/v1.7.0" 15 | "go.opentelemetry.io/otel/trace" 16 | ) 17 | 18 | // Initializes an OTLP exporter, and configures the corresponding trace and 19 | // metric providers. 20 | func initProvider() func() { 21 | ctx := context.Background() 22 | 23 | res, err := resource.New(ctx, 24 | resource.WithAttributes( 25 | // the service name used to display traces in backends 26 | semconv.ServiceNameKey.String("test-server"), 27 | ), 28 | ) 29 | handleErr(err, "failed to create resource") 30 | 31 | // If the OpenTelemetry Collector is running on a local cluster (minikube or 32 | // microk8s), it should be accessible through the NodePort service at the 33 | // `localhost:30080` endpoint. Otherwise, replace `localhost` with the 34 | // endpoint of your cluster. If you run the app inside k8s, then you can 35 | // probably connect directly to the service through dns 36 | 37 | // Set up a trace exporter 38 | traceExporter, err := otlptracehttp.New(ctx, otlptracehttp.WithEndpoint("otel-collector:4317"), otlptracehttp.WithInsecure(), 39 | otlptracehttp.WithHeaders(map[string]string{ 40 | "Content-Type": "application/json", 41 | })) 42 | handleErr(err, "failed to create trace exporter") 43 | 44 | // Register the trace exporter with a TracerProvider, using a batch 45 | // span processor to aggregate spans before export. 46 | bsp := sdktrace.NewBatchSpanProcessor(traceExporter) 47 | tracerProvider := sdktrace.NewTracerProvider( 48 | sdktrace.WithSampler(sdktrace.AlwaysSample()), 49 | sdktrace.WithResource(res), 50 | sdktrace.WithSpanProcessor(bsp), 51 | ) 52 | otel.SetTracerProvider(tracerProvider) 53 | 54 | // set global propagator to tracecontext (the default is no-op). 55 | otel.SetTextMapPropagator(propagation.TraceContext{}) 56 | 57 | return func() { 58 | // Shutdown will flush any remaining spans and shut down the exporter. 59 | handleErr(tracerProvider.Shutdown(ctx), "failed to shutdown TracerProvider") 60 | } 61 | } 62 | 63 | func main() { 64 | log.Printf("Waiting for connection...") 65 | 66 | shutdown := initProvider() 67 | defer shutdown() 68 | 69 | tracer := otel.Tracer("test-client-tracer") 70 | 71 | http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { 72 | log.Println("request url path: ", request.URL.Path) 73 | log.Println("request headers: ", request.Header) 74 | 75 | ctx := propagation.TraceContext{}.Extract(request.Context(), propagation.HeaderCarrier(request.Header)) 76 | ctx, span := tracer.Start(ctx, "test-server-span", trace.WithSpanKind(trace.SpanKindServer)) 77 | defer span.End() 78 | 79 | time.Sleep(2 * time.Second) 80 | 81 | writer.WriteHeader(200) 82 | _, _ = writer.Write([]byte("hello lua")) 83 | }) 84 | 85 | _ = http.ListenAndServe("0.0.0.0:80", nil) 86 | } 87 | 88 | func handleErr(err error, message string) { 89 | if err != nil { 90 | log.Fatalf("%s: %v", message, err) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Changelog 3 | --- 4 | 5 | ## Table of Contents 6 | 7 | - [0.2-6](#0.2-6) 8 | - [0.2-5](#0.2-5) 9 | - [0.2-4](#0.2-4) 10 | - [0.2-3](#0.2-3) 11 | - [0.2-2](#0.2-2) 12 | - [0.2-1](#0.2-1) 13 | - [0.2-0](#0.2-0) 14 | - [0.1-3](#0.1-3) 15 | - [0.1-2](#0.1-2) 16 | - [0.1-1](#0.1-1) 17 | - [0.1-0](#0.1-0) 18 | 19 | 20 | ## 0.2-6 21 | 22 | ### Change 23 | 24 | - feature: allow user to specify start time for new recording span yangxikun/opentelemetry-lua#86 25 | 26 | ### Bugfix 27 | 28 | - duplicate trace ids in high requests distributed system yangxikun/opentelemetry-lua#95 29 | 30 | 31 | ## 0.2-5 32 | 33 | ### Change 34 | 35 | - feature: attribute support array_value type yangxikun/opentelemetry-lua#82 36 | - improve: add resource attrs to console export yangxikun/opentelemetry-lua#91 37 | - improve: allow multiple tracer providers or tracers for export yangxikun/opentelemetry-lua#92 38 | 39 | ### Bugfix 40 | 41 | - Baggage header parsing should remove leading and trailing whitespaces in k/v, yangxikun/opentelemetry-lua#77 42 | 43 | ## 0.2-4 44 | 45 | ### Change 46 | 47 | - improve: speed up id generator yangxikun/opentelemetry-lua#74 48 | - feature: add semantic conventions yangxikun/opentelemetry-lua#66 49 | 50 | ## 0.2-3 51 | 52 | ### Change 53 | 54 | - feature: exporter client support https yangxikun/opentelemetry-lua#60 55 | - breaking: span_status field name changed to uppercase yangxikun/opentelemetry-lua#59 56 | 57 | ### Bugfix 58 | 59 | - context.with_span need copy the parent context's entries yangxikun/opentelemetry-lua#58 60 | 61 | ## 0.2-2 62 | 63 | ### Change 64 | 65 | - feature: add simple span processor 66 | - enhancement: allow users to specify multiple span processors 67 | - enhancement: allow user to specify end timestamp when finishing span 68 | - feature: add tracestate handling 69 | 70 | ## 0.2-1 71 | 72 | ### Change 73 | 74 | - enhancement: trace exporter add exponential backoff and circuit breaker when failed to exporting spans 75 | - feature: add console exporter for debugging 76 | - feature: support [baggage](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/baggage/api.md) 77 | - feature: add metrics reporter 78 | - feature: add tracestate to exports 79 | - breaking: refactor context, `context.attach` will return a token, and need to be passed to `context.detach` 80 | 81 | ### Bugfix 82 | 83 | - fix data race in batch_span_processor 84 | 85 | ## 0.2-0 86 | 87 | ### Change 88 | 89 | - enhancement: support export spans on exit 90 | - enhancement: exporter http_client use keepalive 91 | - breaking: propagation api change 92 | 93 | ## 0.1-3 94 | 95 | ### Bugfix 96 | 97 | - exporter: hex2bytes may panic if traceID is invalid 98 | 99 | ## 0.1-2 100 | 101 | ### Bugfix 102 | 103 | - batch_span_processor export zero length spans. apache/apisix#6329 104 | 105 | ## 0.1-1 106 | 107 | ### Bugfix 108 | 109 | - batch_span_processor fail on openresty log_by_lua phase 110 | - batch_span_processor.shutdown should not set exporter=nil 111 | 112 | ### Change 113 | 114 | - enhancement: batch_span_processor avoid useless timer 115 | - feat: exporter http client support custom headers 116 | - feat: refactor id_generator 117 | 118 | ## 0.1-0 -------------------------------------------------------------------------------- /rockspec/opentelemetry-lua-0.1-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "opentelemetry-lua" 2 | version = "0.1-0" 3 | source = { 4 | url = "git://github.com/yangxikun/opentelemetry-lua", 5 | tag = "v0.1.0" 6 | } 7 | 8 | description = { 9 | summary = "The OpenTelemetry Lua SDK", 10 | homepage = "https://github.com/yangxikun/opentelemetry-lua", 11 | license = "Apache License 2.0" 12 | } 13 | 14 | dependencies = { 15 | "lua-protobuf = 0.3.3", 16 | "api7-lua-resty-http = 0.2.0", 17 | } 18 | 19 | build = { 20 | type = "builtin", 21 | modules = { 22 | ["opentelemetry.global"] = "lib/opentelemetry/global.lua", 23 | ["opentelemetry.context"] = "lib/opentelemetry/context.lua", 24 | ["opentelemetry.context_storage"] = "lib/opentelemetry/context_storage.lua", 25 | ["opentelemetry.attribute"] = "lib/opentelemetry/attribute.lua", 26 | ["opentelemetry.instrumentation_library"] = "lib/opentelemetry/instrumentation_library.lua", 27 | ["opentelemetry.resource"] = "lib/opentelemetry/resource.lua", 28 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 29 | ["opentelemetry.trace.event"] = "lib/opentelemetry/trace/event.lua", 30 | ["opentelemetry.trace.exporter.http_client"] = "lib/opentelemetry/trace/exporter/http_client.lua", 31 | ["opentelemetry.trace.exporter.otlp"] = "lib/opentelemetry/trace/exporter/otlp.lua", 32 | ["opentelemetry.trace.exporter.pb"] = "lib/opentelemetry/trace/exporter/pb.lua", 33 | ["opentelemetry.trace.id_generator"] = "lib/opentelemetry/trace/id_generator.lua", 34 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 35 | ["opentelemetry.trace.non_recording_span"] = "lib/opentelemetry/trace/non_recording_span.lua", 36 | ["opentelemetry.trace.noop_span"] = "lib/opentelemetry/trace/noop_span.lua", 37 | ["opentelemetry.trace.propagation.carrier"] = "lib/opentelemetry/trace/propagation/carrier.lua", 38 | ["opentelemetry.trace.propagation.trace_context"] = "lib/opentelemetry/trace/propagation/trace_context.lua", 39 | ["opentelemetry.trace.recording_span"] = "lib/opentelemetry/trace/recording_span.lua", 40 | ["opentelemetry.trace.sampling.always_off_sampler"] = "lib/opentelemetry/trace/sampling/always_off_sampler.lua", 41 | ["opentelemetry.trace.sampling.always_on_sampler"] = "lib/opentelemetry/trace/sampling/always_on_sampler.lua", 42 | ["opentelemetry.trace.sampling.parent_base_sampler"] = "lib/opentelemetry/trace/sampling/parent_base_sampler.lua", 43 | ["opentelemetry.trace.sampling.result"] = "lib/opentelemetry/trace/sampling/result.lua", 44 | ["opentelemetry.trace.sampling.trace_id_ratio_sampler"] = "lib/opentelemetry/trace/sampling/trace_id_ratio_sampler.lua", 45 | ["opentelemetry.trace.span_context"] = "lib/opentelemetry/trace/span_context.lua", 46 | ["opentelemetry.trace.span_kind"] = "lib/opentelemetry/trace/span_kind.lua", 47 | ["opentelemetry.trace.span_status"] = "lib/opentelemetry/trace/span_status.lua", 48 | ["opentelemetry.trace.tracer"] = "lib/opentelemetry/trace/tracer.lua", 49 | ["opentelemetry.trace.tracer_provider"] = "lib/opentelemetry/trace/tracer_provider.lua", 50 | ["opentelemetry.util"] = "lib/opentelemetry/util.lua" 51 | } 52 | } -------------------------------------------------------------------------------- /rockspec/opentelemetry-lua-0.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "opentelemetry-lua" 2 | version = "0.1-1" 3 | source = { 4 | url = "git://github.com/yangxikun/opentelemetry-lua", 5 | tag = "v0.1.1" 6 | } 7 | 8 | description = { 9 | summary = "The OpenTelemetry Lua SDK", 10 | homepage = "https://github.com/yangxikun/opentelemetry-lua", 11 | license = "Apache License 2.0" 12 | } 13 | 14 | dependencies = { 15 | "lua-protobuf = 0.3.3", 16 | "api7-lua-resty-http = 0.2.0", 17 | } 18 | 19 | build = { 20 | type = "builtin", 21 | modules = { 22 | ["opentelemetry.global"] = "lib/opentelemetry/global.lua", 23 | ["opentelemetry.context"] = "lib/opentelemetry/context.lua", 24 | ["opentelemetry.context_storage"] = "lib/opentelemetry/context_storage.lua", 25 | ["opentelemetry.attribute"] = "lib/opentelemetry/attribute.lua", 26 | ["opentelemetry.instrumentation_library"] = "lib/opentelemetry/instrumentation_library.lua", 27 | ["opentelemetry.resource"] = "lib/opentelemetry/resource.lua", 28 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 29 | ["opentelemetry.trace.event"] = "lib/opentelemetry/trace/event.lua", 30 | ["opentelemetry.trace.exporter.http_client"] = "lib/opentelemetry/trace/exporter/http_client.lua", 31 | ["opentelemetry.trace.exporter.otlp"] = "lib/opentelemetry/trace/exporter/otlp.lua", 32 | ["opentelemetry.trace.exporter.pb"] = "lib/opentelemetry/trace/exporter/pb.lua", 33 | ["opentelemetry.trace.id_generator"] = "lib/opentelemetry/trace/id_generator.lua", 34 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 35 | ["opentelemetry.trace.non_recording_span"] = "lib/opentelemetry/trace/non_recording_span.lua", 36 | ["opentelemetry.trace.noop_span"] = "lib/opentelemetry/trace/noop_span.lua", 37 | ["opentelemetry.trace.propagation.carrier"] = "lib/opentelemetry/trace/propagation/carrier.lua", 38 | ["opentelemetry.trace.propagation.trace_context"] = "lib/opentelemetry/trace/propagation/trace_context.lua", 39 | ["opentelemetry.trace.recording_span"] = "lib/opentelemetry/trace/recording_span.lua", 40 | ["opentelemetry.trace.sampling.always_off_sampler"] = "lib/opentelemetry/trace/sampling/always_off_sampler.lua", 41 | ["opentelemetry.trace.sampling.always_on_sampler"] = "lib/opentelemetry/trace/sampling/always_on_sampler.lua", 42 | ["opentelemetry.trace.sampling.parent_base_sampler"] = "lib/opentelemetry/trace/sampling/parent_base_sampler.lua", 43 | ["opentelemetry.trace.sampling.result"] = "lib/opentelemetry/trace/sampling/result.lua", 44 | ["opentelemetry.trace.sampling.trace_id_ratio_sampler"] = "lib/opentelemetry/trace/sampling/trace_id_ratio_sampler.lua", 45 | ["opentelemetry.trace.span_context"] = "lib/opentelemetry/trace/span_context.lua", 46 | ["opentelemetry.trace.span_kind"] = "lib/opentelemetry/trace/span_kind.lua", 47 | ["opentelemetry.trace.span_status"] = "lib/opentelemetry/trace/span_status.lua", 48 | ["opentelemetry.trace.tracer"] = "lib/opentelemetry/trace/tracer.lua", 49 | ["opentelemetry.trace.tracer_provider"] = "lib/opentelemetry/trace/tracer_provider.lua", 50 | ["opentelemetry.util"] = "lib/opentelemetry/util.lua" 51 | } 52 | } -------------------------------------------------------------------------------- /rockspec/opentelemetry-lua-0.1-2.rockspec: -------------------------------------------------------------------------------- 1 | package = "opentelemetry-lua" 2 | version = "0.1-2" 3 | source = { 4 | url = "git://github.com/yangxikun/opentelemetry-lua", 5 | tag = "v0.1.2" 6 | } 7 | 8 | description = { 9 | summary = "The OpenTelemetry Lua SDK", 10 | homepage = "https://github.com/yangxikun/opentelemetry-lua", 11 | license = "Apache License 2.0" 12 | } 13 | 14 | dependencies = { 15 | "lua-protobuf = 0.3.3", 16 | "api7-lua-resty-http = 0.2.0", 17 | } 18 | 19 | build = { 20 | type = "builtin", 21 | modules = { 22 | ["opentelemetry.global"] = "lib/opentelemetry/global.lua", 23 | ["opentelemetry.context"] = "lib/opentelemetry/context.lua", 24 | ["opentelemetry.context_storage"] = "lib/opentelemetry/context_storage.lua", 25 | ["opentelemetry.attribute"] = "lib/opentelemetry/attribute.lua", 26 | ["opentelemetry.instrumentation_library"] = "lib/opentelemetry/instrumentation_library.lua", 27 | ["opentelemetry.resource"] = "lib/opentelemetry/resource.lua", 28 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 29 | ["opentelemetry.trace.event"] = "lib/opentelemetry/trace/event.lua", 30 | ["opentelemetry.trace.exporter.http_client"] = "lib/opentelemetry/trace/exporter/http_client.lua", 31 | ["opentelemetry.trace.exporter.otlp"] = "lib/opentelemetry/trace/exporter/otlp.lua", 32 | ["opentelemetry.trace.exporter.pb"] = "lib/opentelemetry/trace/exporter/pb.lua", 33 | ["opentelemetry.trace.id_generator"] = "lib/opentelemetry/trace/id_generator.lua", 34 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 35 | ["opentelemetry.trace.non_recording_span"] = "lib/opentelemetry/trace/non_recording_span.lua", 36 | ["opentelemetry.trace.noop_span"] = "lib/opentelemetry/trace/noop_span.lua", 37 | ["opentelemetry.trace.propagation.carrier"] = "lib/opentelemetry/trace/propagation/carrier.lua", 38 | ["opentelemetry.trace.propagation.trace_context"] = "lib/opentelemetry/trace/propagation/trace_context.lua", 39 | ["opentelemetry.trace.recording_span"] = "lib/opentelemetry/trace/recording_span.lua", 40 | ["opentelemetry.trace.sampling.always_off_sampler"] = "lib/opentelemetry/trace/sampling/always_off_sampler.lua", 41 | ["opentelemetry.trace.sampling.always_on_sampler"] = "lib/opentelemetry/trace/sampling/always_on_sampler.lua", 42 | ["opentelemetry.trace.sampling.parent_base_sampler"] = "lib/opentelemetry/trace/sampling/parent_base_sampler.lua", 43 | ["opentelemetry.trace.sampling.result"] = "lib/opentelemetry/trace/sampling/result.lua", 44 | ["opentelemetry.trace.sampling.trace_id_ratio_sampler"] = "lib/opentelemetry/trace/sampling/trace_id_ratio_sampler.lua", 45 | ["opentelemetry.trace.span_context"] = "lib/opentelemetry/trace/span_context.lua", 46 | ["opentelemetry.trace.span_kind"] = "lib/opentelemetry/trace/span_kind.lua", 47 | ["opentelemetry.trace.span_status"] = "lib/opentelemetry/trace/span_status.lua", 48 | ["opentelemetry.trace.tracer"] = "lib/opentelemetry/trace/tracer.lua", 49 | ["opentelemetry.trace.tracer_provider"] = "lib/opentelemetry/trace/tracer_provider.lua", 50 | ["opentelemetry.util"] = "lib/opentelemetry/util.lua" 51 | } 52 | } -------------------------------------------------------------------------------- /rockspec/opentelemetry-lua-0.1-3.rockspec: -------------------------------------------------------------------------------- 1 | package = "opentelemetry-lua" 2 | version = "0.1-3" 3 | source = { 4 | url = "git://github.com/yangxikun/opentelemetry-lua", 5 | tag = "v0.1.3" 6 | } 7 | 8 | description = { 9 | summary = "The OpenTelemetry Lua SDK", 10 | homepage = "https://github.com/yangxikun/opentelemetry-lua", 11 | license = "Apache License 2.0" 12 | } 13 | 14 | dependencies = { 15 | "lua-protobuf = 0.3.3", 16 | "api7-lua-resty-http = 0.2.0", 17 | } 18 | 19 | build = { 20 | type = "builtin", 21 | modules = { 22 | ["opentelemetry.global"] = "lib/opentelemetry/global.lua", 23 | ["opentelemetry.context"] = "lib/opentelemetry/context.lua", 24 | ["opentelemetry.context_storage"] = "lib/opentelemetry/context_storage.lua", 25 | ["opentelemetry.attribute"] = "lib/opentelemetry/attribute.lua", 26 | ["opentelemetry.instrumentation_library"] = "lib/opentelemetry/instrumentation_library.lua", 27 | ["opentelemetry.resource"] = "lib/opentelemetry/resource.lua", 28 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 29 | ["opentelemetry.trace.event"] = "lib/opentelemetry/trace/event.lua", 30 | ["opentelemetry.trace.exporter.http_client"] = "lib/opentelemetry/trace/exporter/http_client.lua", 31 | ["opentelemetry.trace.exporter.otlp"] = "lib/opentelemetry/trace/exporter/otlp.lua", 32 | ["opentelemetry.trace.exporter.pb"] = "lib/opentelemetry/trace/exporter/pb.lua", 33 | ["opentelemetry.trace.id_generator"] = "lib/opentelemetry/trace/id_generator.lua", 34 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 35 | ["opentelemetry.trace.non_recording_span"] = "lib/opentelemetry/trace/non_recording_span.lua", 36 | ["opentelemetry.trace.noop_span"] = "lib/opentelemetry/trace/noop_span.lua", 37 | ["opentelemetry.trace.propagation.carrier"] = "lib/opentelemetry/trace/propagation/carrier.lua", 38 | ["opentelemetry.trace.propagation.trace_context"] = "lib/opentelemetry/trace/propagation/trace_context.lua", 39 | ["opentelemetry.trace.recording_span"] = "lib/opentelemetry/trace/recording_span.lua", 40 | ["opentelemetry.trace.sampling.always_off_sampler"] = "lib/opentelemetry/trace/sampling/always_off_sampler.lua", 41 | ["opentelemetry.trace.sampling.always_on_sampler"] = "lib/opentelemetry/trace/sampling/always_on_sampler.lua", 42 | ["opentelemetry.trace.sampling.parent_base_sampler"] = "lib/opentelemetry/trace/sampling/parent_base_sampler.lua", 43 | ["opentelemetry.trace.sampling.result"] = "lib/opentelemetry/trace/sampling/result.lua", 44 | ["opentelemetry.trace.sampling.trace_id_ratio_sampler"] = "lib/opentelemetry/trace/sampling/trace_id_ratio_sampler.lua", 45 | ["opentelemetry.trace.span_context"] = "lib/opentelemetry/trace/span_context.lua", 46 | ["opentelemetry.trace.span_kind"] = "lib/opentelemetry/trace/span_kind.lua", 47 | ["opentelemetry.trace.span_status"] = "lib/opentelemetry/trace/span_status.lua", 48 | ["opentelemetry.trace.tracer"] = "lib/opentelemetry/trace/tracer.lua", 49 | ["opentelemetry.trace.tracer_provider"] = "lib/opentelemetry/trace/tracer_provider.lua", 50 | ["opentelemetry.util"] = "lib/opentelemetry/util.lua" 51 | } 52 | } -------------------------------------------------------------------------------- /lib/opentelemetry/trace/exporter/encoder.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------- 2 | -- The encoder is responsible for taking spans and serializing them into 3 | -- different export formats. 4 | ------------------------------------------------------------------------------- 5 | 6 | local util = require("opentelemetry.util") 7 | 8 | local _M = { 9 | } 10 | 11 | local mt = { 12 | __index = _M 13 | } 14 | 15 | -------------------------------------------------------------------------------- 16 | -- hex2bytes converts a hex string into bytes (used for transit over OTLP). 17 | -- 18 | -- @param str Hex string. 19 | -- @return Table to be used as basis for more specific exporters. 20 | -------------------------------------------------------------------------------- 21 | local function hex2bytes(str) 22 | return (str:gsub('..', function(cc) 23 | local n = tonumber(cc, 16) 24 | if n then 25 | return string.char(n) 26 | end 27 | end)) 28 | end 29 | 30 | -------------------------------------------------------------------------------- 31 | -- for_export structures span data for export; used as basis for more specific 32 | -- exporters. 33 | -- 34 | -- @param span The span to export 35 | -- @return Table to be used as basis for more specific exporters. 36 | -------------------------------------------------------------------------------- 37 | function _M.for_export(span) 38 | return { 39 | trace_id = span.ctx.trace_id, 40 | span_id = span.ctx.span_id, 41 | trace_state = span.ctx.trace_state:as_string(), 42 | parent_span_id = span.parent_ctx.span_id or "", 43 | name = span.name, 44 | kind = span.kind, 45 | start_time_unix_nano = string.format("%d", span.start_time), 46 | end_time_unix_nano = string.format("%d", span.end_time), 47 | attributes = span.attributes, 48 | dropped_attributes_count = 0, 49 | events = span.events, 50 | dropped_events_count = 0, 51 | links = {}, 52 | dropped_links_count = 0, 53 | status = span.status 54 | } 55 | end 56 | 57 | -------------------------------------------------------------------------------- 58 | -- for_otlp returns a table that can be protobuf-encoded for transmission over 59 | -- OTLP. 60 | -- 61 | -- @param span The span to export 62 | -- @return Table to be protobuf-encoded 63 | -------------------------------------------------------------------------------- 64 | function _M.for_otlp(span) 65 | local ret = _M.for_export(span) 66 | ret.trace_id = hex2bytes(ret.trace_id) 67 | ret.span_id = hex2bytes(ret.span_id) 68 | ret.parent_span_id = hex2bytes(ret.parent_span_id) 69 | return ret 70 | end 71 | 72 | -------------------------------------------------------------------------------- 73 | -- for_console renders a string representation of span for console output. 74 | -- 75 | -- @param span The span to export 76 | -- @return String representation of span. 77 | -------------------------------------------------------------------------------- 78 | function _M.for_console(span) 79 | local ret = "\n---------------------------------------------------------\n" 80 | local ex = _M.for_export(span) 81 | ex["resource_attributes"] = span.tracer.provider.resource.attrs 82 | ret = ret .. util.table_as_string(ex, 2) 83 | ret = ret .. "---------------------------------------------------------\n" 84 | return ret 85 | end 86 | 87 | return _M 88 | -------------------------------------------------------------------------------- /lib/opentelemetry/semantic_conventions/trace/database.lua: -------------------------------------------------------------------------------- 1 | --- This file was automatically generated by utils/generate_semantic_conventions.lua 2 | -- See: https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions 3 | -- 4 | -- module @semantic_conventions.trace.database 5 | local _M = { 6 | -- An identifier for the database management system (DBMS) product being used. See below for a list of well-known identifiers. 7 | DB_SYSTEM = "db.system", 8 | -- The connection string used to connect to the database. It is recommended to remove embedded credentials. 9 | DB_CONNECTION_STRING = "db.connection_string", 10 | -- Username for accessing the database. 11 | DB_USER = "db.user", 12 | -- The fully-qualified class name of the [Java Database Connectivity (JDBC)](https://docs.oracle.com/javase/8/docs/technotes/guides/jdbc/) driver used to connect. 13 | DB_JDBC_DRIVER_CLASSNAME = "db.jdbc.driver_classname", 14 | -- This attribute is used to report the name of the database being accessed. For commands that switch the database, this should be set to the target database (even if the command fails). 15 | DB_NAME = "db.name", 16 | -- The database statement being executed. 17 | DB_STATEMENT = "db.statement", 18 | -- The name of the operation being executed, e.g. the [MongoDB command name](https://docs.mongodb.com/manual/reference/command/#database-operations) such as `findAndModify`, or the SQL keyword. 19 | DB_OPERATION = "db.operation", 20 | -- The Microsoft SQL Server [instance name](https://docs.microsoft.com/en-us/sql/connect/jdbc/building-the-connection-url?view=sql-server-ver15) connecting to. This name is used to determine the port of a named instance. 21 | DB_MSSQL_INSTANCE_NAME = "db.mssql.instance_name", 22 | -- The fetch size used for paging, i.e. how many rows will be returned at once. 23 | DB_CASSANDRA_PAGE_SIZE = "db.cassandra.page_size", 24 | -- The consistency level of the query. Based on consistency values from [CQL](https://docs.datastax.com/en/cassandra-oss/3.0/cassandra/dml/dmlConfigConsistency.html). 25 | DB_CASSANDRA_CONSISTENCY_LEVEL = "db.cassandra.consistency_level", 26 | -- The name of the primary table that the operation is acting upon, including the keyspace name (if applicable). 27 | DB_CASSANDRA_TABLE = "db.cassandra.table", 28 | -- Whether or not the query is idempotent. 29 | DB_CASSANDRA_IDEMPOTENCE = "db.cassandra.idempotence", 30 | -- The number of times a query was speculatively executed. Not set or `0` if the query was not executed speculatively. 31 | DB_CASSANDRA_SPECULATIVE_EXECUTION_COUNT = "db.cassandra.speculative_execution_count", 32 | -- The ID of the coordinating node for a query. 33 | DB_CASSANDRA_COORDINATOR_ID = "db.cassandra.coordinator.id", 34 | -- The data center of the coordinating node for a query. 35 | DB_CASSANDRA_COORDINATOR_DC = "db.cassandra.coordinator.dc", 36 | -- The index of the database being accessed as used in the [`SELECT` command](https://redis.io/commands/select), provided as an integer. To be used instead of the generic `db.name` attribute. 37 | DB_REDIS_DATABASE_INDEX = "db.redis.database_index", 38 | -- The collection being accessed within the database stated in `db.name`. 39 | DB_MONGODB_COLLECTION = "db.mongodb.collection", 40 | -- The name of the primary table that the operation is acting upon, including the database name (if applicable). 41 | DB_SQL_TABLE = "db.sql.table" 42 | } 43 | return _M 44 | -------------------------------------------------------------------------------- /examples/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | "os" 9 | 10 | "go.opentelemetry.io/otel" 11 | "go.opentelemetry.io/otel/codes" 12 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" 13 | "go.opentelemetry.io/otel/propagation" 14 | "go.opentelemetry.io/otel/sdk/resource" 15 | sdktrace "go.opentelemetry.io/otel/sdk/trace" 16 | semconv "go.opentelemetry.io/otel/semconv/v1.7.0" 17 | "go.opentelemetry.io/otel/trace" 18 | ) 19 | 20 | // Initializes an OTLP exporter, and configures the corresponding trace and 21 | // metric providers. 22 | func initProvider() func() { 23 | ctx := context.Background() 24 | 25 | res, err := resource.New(ctx, 26 | resource.WithAttributes( 27 | // the service name used to display traces in backends 28 | semconv.ServiceNameKey.String("test-client"), 29 | ), 30 | ) 31 | handleErr(err, "failed to create resource") 32 | 33 | // If the OpenTelemetry Collector is running on a local cluster (minikube or 34 | // microk8s), it should be accessible through the NodePort service at the 35 | // `localhost:30080` endpoint. Otherwise, replace `localhost` with the 36 | // endpoint of your cluster. If you run the app inside k8s, then you can 37 | // probably connect directly to the service through dns 38 | 39 | // Set up a trace exporter 40 | traceExporter, err := otlptracehttp.New(ctx, otlptracehttp.WithEndpoint("otel-collector:4317"), otlptracehttp.WithInsecure(), 41 | otlptracehttp.WithHeaders(map[string]string{ 42 | "Content-Type": "application/json", 43 | })) 44 | handleErr(err, "failed to create trace exporter") 45 | 46 | // Register the trace exporter with a TracerProvider, using a batch 47 | // span processor to aggregate spans before export. 48 | bsp := sdktrace.NewBatchSpanProcessor(traceExporter) 49 | tracerProvider := sdktrace.NewTracerProvider( 50 | sdktrace.WithSampler(sdktrace.AlwaysSample()), 51 | sdktrace.WithResource(res), 52 | sdktrace.WithSpanProcessor(bsp), 53 | ) 54 | otel.SetTracerProvider(tracerProvider) 55 | 56 | // set global propagator to tracecontext (the default is no-op). 57 | otel.SetTextMapPropagator(propagation.TraceContext{}) 58 | 59 | return func() { 60 | // Shutdown will flush any remaining spans and shut down the exporter. 61 | handleErr(tracerProvider.Shutdown(ctx), "failed to shutdown TracerProvider") 62 | } 63 | } 64 | 65 | func main() { 66 | shutdown := initProvider() 67 | defer shutdown() 68 | 69 | tracer := otel.Tracer("test-client-tracer") 70 | traceState, err := trace.ParseTraceState("trace-state-example-key=trace-state-example-val") 71 | handleErr(err, "parse trace state failed") 72 | ctx := trace.ContextWithSpanContext(context.Background(), trace.NewSpanContext(trace.SpanContextConfig{ 73 | TraceState: traceState, 74 | })) 75 | ctx, span := tracer.Start(ctx, "test-client-span", trace.WithSpanKind(trace.SpanKindClient)) 76 | defer span.End() 77 | 78 | span.SetStatus(codes.Error, "client side failed") 79 | 80 | req, err := http.NewRequest("GET", os.Getenv("PROXY_ENDPOINT"), nil) 81 | handleErr(err, "new request fail") 82 | 83 | propagation.TraceContext{}.Inject(ctx, propagation.HeaderCarrier(req.Header)) 84 | rsp, err := http.DefaultClient.Do(req) 85 | handleErr(err, "request fail") 86 | 87 | body, err := ioutil.ReadAll(rsp.Body) 88 | handleErr(err, "read body fail") 89 | 90 | log.Println(string(body)) 91 | _ = rsp.Body.Close() 92 | } 93 | 94 | func handleErr(err error, message string) { 95 | if err != nil { 96 | log.Fatalf("%s: %v", message, err) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /spec/trace/propagation/text_map/composite_propagator_spec.lua: -------------------------------------------------------------------------------- 1 | local baggage = require "opentelemetry.baggage" 2 | local tracer_provider = require "opentelemetry.trace.tracer_provider" 3 | local composite_propagator = require "opentelemetry.trace.propagation.text_map.composite_propagator" 4 | local text_map_propagator = require "opentelemetry.trace.propagation.text_map.trace_context_propagator" 5 | local baggage_propagator = require "opentelemetry.baggage.propagation.text_map.baggage_propagator" 6 | local noop_propagator = require "opentelemetry.trace.propagation.text_map.noop_propagator" 7 | local context = require("opentelemetry.context") 8 | 9 | -- We're setting these on ngx.req but we aren't running openresty in these 10 | -- tests, so we'll mock that out (ngx.req supports get_headers() and set_header(header_name)) 11 | local function newCarrier(headers_table) 12 | local r = { headers = {} } 13 | r.headers = headers_table 14 | r.get_headers = function() return r.headers end 15 | r.set_header = function(name, val) r.headers[name] = val end 16 | return r 17 | end 18 | 19 | -- We'll need to add more propagators to the repo (Jaeger, B3, etc), in order to 20 | -- fully test this. 21 | describe("composite propagator", function() 22 | describe(".inject", function() 23 | local tmp = text_map_propagator.new() 24 | local bp = baggage_propagator.new() 25 | local np = noop_propagator.new() 26 | local cp = composite_propagator.new({ tmp, bp, np }) 27 | local ctx = context.new() 28 | local tracer_provider = tracer_provider.new() 29 | local tracer = tracer_provider:tracer("test tracer") 30 | 31 | -- start a trace 32 | local new_ctx = tracer:start(ctx, "span", { kind = 1 }) 33 | local bgg = baggage.new({ foo = { value = "bar", metadata = "ok" } }) 34 | new_ctx = new_ctx:inject_baggage(bgg) 35 | 36 | -- figure out what traceheader should look like 37 | local span_context = new_ctx.sp:context() 38 | local traceparent = string.format("00-%s-%s-%02x", 39 | span_context.trace_id, 40 | span_context.span_id, 41 | span_context.trace_flags) 42 | local carrier = newCarrier({}) 43 | 44 | it("should add headers for each propagator", function() 45 | cp:inject(new_ctx, carrier) 46 | assert.are.same( 47 | carrier.get_headers()["traceparent"], 48 | traceparent 49 | ) 50 | assert.are.same( 51 | carrier.get_headers()["baggage"], 52 | "foo=bar;ok" 53 | ) 54 | end) 55 | end) 56 | 57 | describe(".extract", function() 58 | it("should extract headers for each propagator", function() 59 | local tmp = text_map_propagator.new() 60 | local bp = baggage_propagator.new() 61 | local np = noop_propagator.new() 62 | local cp = composite_propagator.new({ tmp, bp, np }) 63 | local trace_id = "10f5b3bcfe3f0c2c5e1ef150fe0b5872" 64 | local carrier = newCarrier( 65 | { traceparent = string.format("00-%s-172accbce5f048db-01", trace_id), 66 | baggage = "foo=bar;ok" }) 67 | local ctx = context.new() 68 | local new_ctx = cp:extract(ctx, carrier) 69 | assert.are.same(new_ctx.sp:context().trace_id, trace_id) 70 | assert.are.same(new_ctx:extract_baggage():get_value("foo"), "bar") 71 | end) 72 | end) 73 | end) 74 | -------------------------------------------------------------------------------- /rockspec/opentelemetry-lua-0.2-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "opentelemetry-lua" 2 | version = "0.2-0" 3 | source = { 4 | url = "git://github.com/yangxikun/opentelemetry-lua", 5 | tag = "v0.2.0" 6 | } 7 | 8 | description = { 9 | summary = "The OpenTelemetry Lua SDK", 10 | homepage = "https://github.com/yangxikun/opentelemetry-lua", 11 | license = "Apache License 2.0" 12 | } 13 | 14 | dependencies = { 15 | "lua-protobuf = 0.3.3", 16 | "lua-resty-http = 0.16.1-0", 17 | } 18 | 19 | build = { 20 | type = "builtin", 21 | modules = { 22 | ["opentelemetry.global"] = "lib/opentelemetry/global.lua", 23 | ["opentelemetry.context"] = "lib/opentelemetry/context.lua", 24 | ["opentelemetry.context_storage"] = "lib/opentelemetry/context_storage.lua", 25 | ["opentelemetry.attribute"] = "lib/opentelemetry/attribute.lua", 26 | ["opentelemetry.instrumentation_library"] = "lib/opentelemetry/instrumentation_library.lua", 27 | ["opentelemetry.resource"] = "lib/opentelemetry/resource.lua", 28 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 29 | ["opentelemetry.trace.event"] = "lib/opentelemetry/trace/event.lua", 30 | ["opentelemetry.trace.exporter.http_client"] = "lib/opentelemetry/trace/exporter/http_client.lua", 31 | ["opentelemetry.trace.exporter.otlp"] = "lib/opentelemetry/trace/exporter/otlp.lua", 32 | ["opentelemetry.trace.exporter.pb"] = "lib/opentelemetry/trace/exporter/pb.lua", 33 | ["opentelemetry.trace.id_generator"] = "lib/opentelemetry/trace/id_generator.lua", 34 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 35 | ["opentelemetry.trace.non_recording_span"] = "lib/opentelemetry/trace/non_recording_span.lua", 36 | ["opentelemetry.trace.noop_span"] = "lib/opentelemetry/trace/noop_span.lua", 37 | ["opentelemetry.trace.propagation.text_map.trace_context_propagator"] = "lib/opentelemetry/trace/propagation/text_map/trace_context_propagator.lua", 38 | ["opentelemetry.trace.propagation.text_map.composite_propagator"] = "lib/opentelemetry/trace/propagation/text_map/composite_propagator.lua", 39 | ["opentelemetry.trace.propagation.text_map.getter"] = "lib/opentelemetry/trace/propagation/text_map/getter.lua", 40 | ["opentelemetry.trace.propagation.text_map.setter"] = "lib/opentelemetry/trace/propagation/text_map/setter.lua", 41 | ["opentelemetry.trace.propagation.text_map.noop_propagator"] = "lib/opentelemetry/trace/propagation/text_map/noop_propagator.lua", 42 | ["opentelemetry.trace.recording_span"] = "lib/opentelemetry/trace/recording_span.lua", 43 | ["opentelemetry.trace.sampling.always_off_sampler"] = "lib/opentelemetry/trace/sampling/always_off_sampler.lua", 44 | ["opentelemetry.trace.sampling.always_on_sampler"] = "lib/opentelemetry/trace/sampling/always_on_sampler.lua", 45 | ["opentelemetry.trace.sampling.parent_base_sampler"] = "lib/opentelemetry/trace/sampling/parent_base_sampler.lua", 46 | ["opentelemetry.trace.sampling.result"] = "lib/opentelemetry/trace/sampling/result.lua", 47 | ["opentelemetry.trace.sampling.trace_id_ratio_sampler"] = "lib/opentelemetry/trace/sampling/trace_id_ratio_sampler.lua", 48 | ["opentelemetry.trace.span_context"] = "lib/opentelemetry/trace/span_context.lua", 49 | ["opentelemetry.trace.span_kind"] = "lib/opentelemetry/trace/span_kind.lua", 50 | ["opentelemetry.trace.span_status"] = "lib/opentelemetry/trace/span_status.lua", 51 | ["opentelemetry.trace.tracer"] = "lib/opentelemetry/trace/tracer.lua", 52 | ["opentelemetry.trace.tracer_provider"] = "lib/opentelemetry/trace/tracer_provider.lua", 53 | ["opentelemetry.util"] = "lib/opentelemetry/util.lua" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/tracer_provider.lua: -------------------------------------------------------------------------------- 1 | local instrumentation_library_new = require("opentelemetry.instrumentation_library").new 2 | local resource = require("opentelemetry.resource") 3 | local attr = require("opentelemetry.attribute") 4 | local tracer_new = require("opentelemetry.trace.tracer").new 5 | local id_generator = require("opentelemetry.trace.id_generator") 6 | local always_on_sampler_new = require("opentelemetry.trace.sampling.always_on_sampler").new 7 | local parent_base_sampler_new = require("opentelemetry.trace.sampling.parent_base_sampler").new 8 | 9 | local _M = { 10 | } 11 | 12 | local mt = { 13 | __index = _M 14 | } 15 | 16 | ------------------------------------------------------------------ 17 | -- create a tracer provider. 18 | -- 19 | -- @span_processor [optional] span_processors. Should be table 20 | -- but we allow for a single span_processor 21 | -- for backwards compatibility. 22 | -- @opts [optional] config 23 | -- opts.sampler: opentelemetry.trace.sampling.*, default parent_base_sampler 24 | -- opts.resource 25 | -- @return tracer provider factory 26 | ------------------------------------------------------------------ 27 | function _M.new(span_processors, opts) 28 | if not opts then 29 | opts = {} 30 | end 31 | 32 | -- Handle nil span processors and users that pass single 33 | -- span processor not wrapped in a table. 34 | if span_processors and #span_processors == 0 then 35 | span_processors = { span_processors } 36 | elseif span_processors == nil then 37 | span_processors = {} 38 | end 39 | 40 | local r = resource.new(attr.string("telemetry.sdk.language", "lua"), 41 | attr.string("telemetry.sdk.name", "opentelemetry-lua"), 42 | attr.string("telemetry.sdk.version", "0.1.1")) 43 | 44 | local self = { 45 | span_processors = span_processors, 46 | sampler = opts.sampler or parent_base_sampler_new(always_on_sampler_new()), 47 | resource = resource.merge(opts.resource, r), 48 | id_generator = id_generator, 49 | named_tracer = {}, 50 | } 51 | return setmetatable(self, mt) 52 | end 53 | 54 | ------------------------------------------------------------------ 55 | -- create a tracer. 56 | -- 57 | -- @name tracer name 58 | -- @opts [optional] config 59 | -- opts.version: specifies the version of the instrumentation library 60 | -- opts.schema_url: specifies the Schema URL that should be recorded in the emitted telemetry. 61 | -- @return tracer 62 | ------------------------------------------------------------------ 63 | function _M.tracer(self, name, opts) 64 | if not opts then 65 | opts = { 66 | version = "", 67 | schema_url = "", 68 | } 69 | end 70 | local key = name .. opts.version .. opts.schema_url 71 | local tracer = self.named_tracer[key] 72 | if tracer then 73 | return tracer 74 | end 75 | 76 | self.named_tracer[key] = tracer_new(self, instrumentation_library_new(name, opts.version, opts.schema_url)) 77 | return self.named_tracer[key] 78 | end 79 | 80 | function _M.force_flush(self) 81 | for _, sp in ipairs(self.span_processors) do 82 | sp:force_flush() 83 | end 84 | end 85 | 86 | function _M.shutdown(self) 87 | for _, sp in ipairs(self.span_processors) do 88 | sp:shutdown() 89 | end 90 | end 91 | 92 | function _M.register_span_processor(self, sp) 93 | table.insert(self.span_processors, sp) 94 | end 95 | 96 | function _M.set_span_processors(self, ...) 97 | self.span_processors = { ... } 98 | end 99 | 100 | return _M 101 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | concurrency: 4 | group: ${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | branches: 10 | - "main" 11 | - "update-ci" 12 | pull_request: 13 | branches: 14 | - main 15 | 16 | jobs: 17 | build-openresty: 18 | name: "Build openresty image" 19 | runs-on: ubuntu-20.04 20 | steps: 21 | - uses: actions/checkout@v3 22 | - uses: docker/setup-buildx-action@v1 23 | - uses: docker/build-push-action@v4 24 | with: 25 | context: . 26 | cache-from: type=gha 27 | cache-to: type=gha,mode=max 28 | push: false 29 | outputs: type=docker,dest=/tmp/openresty-image.tar 30 | tags: opentelemetry-lua_openresty 31 | - name: Upload artifact 32 | uses: actions/upload-artifact@v2 33 | with: 34 | name: openresty-image 35 | path: /tmp/openresty-image.tar 36 | end-to-end-tests: 37 | name: "End to end tests" 38 | timeout-minutes: 60 39 | runs-on: ubuntu-latest 40 | needs: 41 | - build-openresty 42 | steps: 43 | - uses: actions/checkout@v3 44 | - uses: docker/setup-buildx-action@v1 45 | - uses: docker/build-push-action@v4 46 | with: 47 | context: examples/server 48 | cache-from: type=gha 49 | cache-to: type=gha,mode=max 50 | push: false 51 | tags: opentelemetry-lua_test-server 52 | - uses: docker/build-push-action@v4 53 | with: 54 | context: examples/client 55 | cache-from: type=gha 56 | cache-to: type=gha,mode=max 57 | push: false 58 | tags: opentelemetry-lua_test-client 59 | - name: Download artifact 60 | uses: actions/download-artifact@v2 61 | with: 62 | name: openresty-image 63 | path: /tmp 64 | - name: Load Docker images 65 | run: | 66 | docker load --input /tmp/openresty-image.tar 67 | docker image ls -a 68 | - name: Start containers 69 | run: make openresty-dev CONTAINER_ORCHESTRATOR_EXEC_OPTIONS="-T" 70 | - name: Run openresty integration tests 71 | run: make openresty-integration-test CONTAINER_ORCHESTRATOR_EXEC_OPTIONS="-T" 72 | - name: Run openresty-test-e2e-trace-context 73 | run: make openresty-test-e2e-trace-context CONTAINER_ORCHESTRATOR_EXEC_OPTIONS="-T" 74 | - name: Run openresty-test-e2e 75 | run: make openresty-test-e2e CONTAINER_ORCHESTRATOR_EXEC_OPTIONS="-T" 76 | 77 | unit-tests: 78 | name: "unit tests" 79 | timeout-minutes: 60 80 | runs-on: ubuntu-latest 81 | needs: 82 | - build-openresty 83 | steps: 84 | - name: Checkout 85 | uses: actions/checkout@v2 86 | - name: Set up Docker Buildx 87 | uses: docker/setup-buildx-action@v1 88 | - name: Download artifact 89 | uses: actions/download-artifact@v2 90 | with: 91 | name: openresty-image 92 | path: /tmp 93 | - name: Load Docker images 94 | run: | 95 | docker load --input /tmp/openresty-image.tar 96 | docker image ls -a 97 | - name: Run busted unit tests 98 | run: make lua-unit-test 99 | - name: Run api tests 100 | run: make api-test 101 | book-keeping: 102 | name: "Book-keeping" 103 | runs-on: ubuntu-20.04 104 | steps: 105 | - uses: actions/checkout@v3 106 | - uses: docker/setup-buildx-action@v1 107 | - uses: docker/build-push-action@v4 108 | with: 109 | context: utils 110 | cache-from: type=gha 111 | cache-to: type=gha,mode=max 112 | push: false 113 | tags: opentelemetry-lua_utils 114 | - run: make check-format 115 | -------------------------------------------------------------------------------- /lib/opentelemetry/semantic_conventions/trace/http.lua: -------------------------------------------------------------------------------- 1 | --- This file was automatically generated by utils/generate_semantic_conventions.lua 2 | -- See: https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions 3 | -- 4 | -- module @semantic_conventions.trace.http 5 | local attribute = require("opentelemetry.attribute") 6 | 7 | local _M = { 8 | -- HTTP request method. 9 | HTTP_METHOD = "http.method", 10 | -- [HTTP response status code](https://tools.ietf.org/html/rfc7231#section-6). 11 | HTTP_STATUS_CODE = "http.status_code", 12 | -- Kind of HTTP protocol used. 13 | HTTP_FLAVOR = "http.flavor", 14 | -- Value of the [HTTP User-Agent](https://www.rfc-editor.org/rfc/rfc9110.html#field.user-agent) header sent by the client. 15 | HTTP_USER_AGENT = "http.user_agent", 16 | -- The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the [Content-Length](https://www.rfc-editor.org/rfc/rfc9110.html#field.content-length) header. For requests using transport encoding, this should be the compressed size. 17 | HTTP_REQUEST_CONTENT_LENGTH = "http.request_content_length", 18 | -- The size of the response payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the [Content-Length](https://www.rfc-editor.org/rfc/rfc9110.html#field.content-length) header. For requests using transport encoding, this should be the compressed size. 19 | HTTP_RESPONSE_CONTENT_LENGTH = "http.response_content_length", 20 | -- Full HTTP request URL in the form `scheme://host[:port]/path?query[#fragment]`. Usually the fragment is not transmitted over HTTP, but if it is known, it should be included nevertheless. 21 | HTTP_URL = "http.url", 22 | -- The ordinal number of request resending attempt (for any reason, including redirects). 23 | HTTP_RESEND_COUNT = "http.resend_count", 24 | -- The URI scheme identifying the used protocol. 25 | HTTP_SCHEME = "http.scheme", 26 | -- The full request target as passed in a HTTP request line or equivalent. 27 | HTTP_TARGET = "http.target", 28 | -- The matched route (path template in the format used by the respective server framework). See note below 29 | HTTP_ROUTE = "http.route", 30 | -- The IP address of the original client behind all proxies, if known (e.g. from [X-Forwarded-For](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For)). 31 | HTTP_CLIENT_IP = "http.client_ip", 32 | -- The opentelemetry attribute key prefix for HTTP request headers 33 | HTTP_REQUEST_HEADER = "http.request.header", 34 | -- The opentelemetry attribute key prefix for HTTP response headers 35 | HTTP_RESPONSE_HEADER = "http.response.header", 36 | } 37 | 38 | ------------------------------------------------------------------ 39 | -- returns the contents of headers as OpenTelemetry attributes. 40 | -- 41 | -- @headers a table of HTTP request headers 42 | -- @return a table of attribute 43 | ------------------------------------------------------------------ 44 | function _M.request_header(headers) 45 | local attributes = {} 46 | for k, v in pairs(headers) do 47 | k = _M.HTTP_REQUEST_HEADER .. '.' .. k 48 | if type(v) == "table" then 49 | table.insert(attributes, attribute.string_array(k, v)) 50 | else 51 | table.insert(attributes, attribute.string(k, v)) 52 | end 53 | end 54 | return attributes 55 | end 56 | 57 | ------------------------------------------------------------------ 58 | -- returns the contents of headers as OpenTelemetry attributes. 59 | -- 60 | -- @headers a table of HTTP response headers 61 | -- @return a table of attribute 62 | ------------------------------------------------------------------ 63 | function _M.response_header(headers) 64 | local attributes = {} 65 | for k, v in pairs(headers) do 66 | k = _M.HTTP_RESPONSE_HEADER .. '.' .. k 67 | if type(v) == "table" then 68 | table.insert(attributes, attribute.string_array(k, v)) 69 | else 70 | table.insert(attributes, attribute.string(k, v)) 71 | end 72 | end 73 | return attributes 74 | end 75 | 76 | return _M 77 | -------------------------------------------------------------------------------- /rockspec/opentelemetry-lua-0.2-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "opentelemetry-lua" 2 | version = "0.2-1" 3 | source = { 4 | url = "git://github.com/yangxikun/opentelemetry-lua", 5 | branch = "v0.2.1", 6 | } 7 | 8 | description = { 9 | summary = "The OpenTelemetry Lua SDK. This is repo's default rockspec, which is used to test the HEAD commit.", 10 | homepage = "https://github.com/yangxikun/opentelemetry-lua", 11 | license = "Apache License 2.0" 12 | } 13 | 14 | dependencies = { 15 | "lua-protobuf = 0.3.3", 16 | "lua-resty-http = 0.16.1-0", 17 | } 18 | 19 | build = { 20 | type = "builtin", 21 | modules = { 22 | ["opentelemetry.global"] = "lib/opentelemetry/global.lua", 23 | ["opentelemetry.context"] = "lib/opentelemetry/context.lua", 24 | ["opentelemetry.attribute"] = "lib/opentelemetry/attribute.lua", 25 | ["opentelemetry.instrumentation_library"] = "lib/opentelemetry/instrumentation_library.lua", 26 | ["opentelemetry.resource"] = "lib/opentelemetry/resource.lua", 27 | ["opentelemetry.metrics_reporter"] = "lib/opentelemetry/metrics_reporter.lua", 28 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 29 | ["opentelemetry.trace.event"] = "lib/opentelemetry/trace/event.lua", 30 | ["opentelemetry.trace.exporter.http_client"] = "lib/opentelemetry/trace/exporter/http_client.lua", 31 | ["opentelemetry.trace.exporter.circuit"] = "lib/opentelemetry/trace/exporter/circuit.lua", 32 | ["opentelemetry.trace.exporter.console"] = "lib/opentelemetry/trace/exporter/console.lua", 33 | ["opentelemetry.trace.exporter.encoder"] = "lib/opentelemetry/trace/exporter/encoder.lua", 34 | ["opentelemetry.trace.exporter.otlp"] = "lib/opentelemetry/trace/exporter/otlp.lua", 35 | ["opentelemetry.trace.exporter.pb"] = "lib/opentelemetry/trace/exporter/pb.lua", 36 | ["opentelemetry.trace.id_generator"] = "lib/opentelemetry/trace/id_generator.lua", 37 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 38 | ["opentelemetry.trace.non_recording_span"] = "lib/opentelemetry/trace/non_recording_span.lua", 39 | ["opentelemetry.trace.noop_span"] = "lib/opentelemetry/trace/noop_span.lua", 40 | ["opentelemetry.trace.propagation.text_map.trace_context_propagator"] = "lib/opentelemetry/trace/propagation/text_map/trace_context_propagator.lua", 41 | ["opentelemetry.trace.propagation.text_map.composite_propagator"] = "lib/opentelemetry/trace/propagation/text_map/composite_propagator.lua", 42 | ["opentelemetry.trace.propagation.text_map.getter"] = "lib/opentelemetry/trace/propagation/text_map/getter.lua", 43 | ["opentelemetry.trace.propagation.text_map.setter"] = "lib/opentelemetry/trace/propagation/text_map/setter.lua", 44 | ["opentelemetry.trace.propagation.text_map.noop_propagator"] = "lib/opentelemetry/trace/propagation/text_map/noop_propagator.lua", 45 | ["opentelemetry.trace.recording_span"] = "lib/opentelemetry/trace/recording_span.lua", 46 | ["opentelemetry.trace.sampling.always_off_sampler"] = "lib/opentelemetry/trace/sampling/always_off_sampler.lua", 47 | ["opentelemetry.trace.sampling.always_on_sampler"] = "lib/opentelemetry/trace/sampling/always_on_sampler.lua", 48 | ["opentelemetry.trace.sampling.parent_base_sampler"] = "lib/opentelemetry/trace/sampling/parent_base_sampler.lua", 49 | ["opentelemetry.trace.sampling.result"] = "lib/opentelemetry/trace/sampling/result.lua", 50 | ["opentelemetry.trace.sampling.trace_id_ratio_sampler"] = "lib/opentelemetry/trace/sampling/trace_id_ratio_sampler.lua", 51 | ["opentelemetry.trace.span_context"] = "lib/opentelemetry/trace/span_context.lua", 52 | ["opentelemetry.trace.span_kind"] = "lib/opentelemetry/trace/span_kind.lua", 53 | ["opentelemetry.trace.span_status"] = "lib/opentelemetry/trace/span_status.lua", 54 | ["opentelemetry.trace.tracer"] = "lib/opentelemetry/trace/tracer.lua", 55 | ["opentelemetry.trace.tracer_provider"] = "lib/opentelemetry/trace/tracer_provider.lua", 56 | ["opentelemetry.baggage"] = "lib/opentelemetry/baggage.lua", 57 | ["opentelemetry.baggage.propagation.text_map.baggage_propagator"] = "lib/opentelemetry/baggage/propagation/text_map/baggage_propagator.lua", 58 | ["opentelemetry.util"] = "lib/opentelemetry/util.lua" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /spec/context_spec.lua: -------------------------------------------------------------------------------- 1 | local context = require("opentelemetry.context") 2 | local otel_global = require("opentelemetry.global") 3 | local baggage = require("opentelemetry.baggage") 4 | 5 | describe("get and set", function() 6 | it("stores and retrieves values at given key", function() 7 | local ctx = context.new() 8 | local new_ctx = ctx:set("key", "value") 9 | assert.are.equal(new_ctx:get("key"), "value") 10 | assert.are_not.equal(ctx, new_ctx) 11 | end) 12 | end) 13 | 14 | describe("current", function() 15 | it("returns last element stored in stack at context_key", function() 16 | local ctx_1 = context.new({ foo = "bar"}) 17 | local ctx_2 = context.new({ foo = "baz"}) 18 | local ctx_storage = { __opentelemetry_context__ = {ctx_1, ctx_2} } 19 | otel_global.set_context_storage(ctx_storage) 20 | assert.are.equal(ctx_2, context.current()) 21 | end) 22 | 23 | it("instantiates different noop spans when no span provided", function() 24 | local ctx_1 = context.new() 25 | local ctx_2 = context.new() 26 | 27 | -- accessing span context on different contexts gives different object back 28 | assert.are_not.equal(ctx_1.sp:context(), ctx_2.sp:context()) 29 | assert.are_not.equal(ctx_1.sp:context().trace_state, ctx_2.sp:context().trace_state) 30 | 31 | -- accessing span context on same context gives same object back 32 | assert.are.equal(ctx_1.sp:context(), ctx_1.sp:context()) 33 | end) 34 | end) 35 | 36 | describe("with_span", function() 37 | it("sets supplied entries on new context", function() 38 | otel_global.set_context_storage({}) 39 | local original_entries = { foo = "bar" } 40 | local old_ctx = context.new(original_entries, "oldspan") 41 | local ctx = old_ctx:with_span("myspan") 42 | assert.are.same(ctx.entries, original_entries) 43 | end) 44 | 45 | it("handles absence of entries arg gracefully", function() 46 | local fake_span = "hi" 47 | local ctx = context:with_span(fake_span) 48 | assert.are.same(ctx.entries, {}) 49 | end) 50 | end) 51 | 52 | describe("attach", function() 53 | it("creates new table at context_key if no table present and returns token matching length of stack after adding element", function() 54 | local ctx_storage = {} 55 | otel_global.set_context_storage(ctx_storage) 56 | local ctx = context.new({ foo = "bar"}) 57 | local token = ctx:attach() 58 | assert.are.equal(token, 1) 59 | end) 60 | 61 | it("appends to existing table at context_key", function() 62 | local ctx_storage = {} 63 | otel_global.set_context_storage(ctx_storage) 64 | local ctx_1 = context.new({ foo = "bar"}) 65 | local ctx_2 = context.new({ foo = "baz"}) 66 | local token_1 = ctx_1:attach() 67 | local token_2 = ctx_2:attach() 68 | assert.are.equal(token_1, 1) 69 | assert.are.equal(token_2, 2) 70 | end) 71 | end) 72 | 73 | describe("detach", function() 74 | it("removes final context from stack at context_key", function() 75 | local ctx_storage = {} 76 | otel_global.set_context_storage(ctx_storage) 77 | local ctx = context.new() 78 | local token = ctx:attach() 79 | local outcome, err = ctx:detach(token) 80 | assert.is_true(outcome) 81 | assert.is_nil(err) 82 | end) 83 | 84 | it("returns outcome of 'false' and error string if token does not match", function() 85 | -- Swallow logs since we are expecting them 86 | stub(ngx, "log") 87 | local ctx_storage = {} 88 | otel_global.set_context_storage(ctx_storage) 89 | local ctx = context.new() 90 | ctx:attach() 91 | local outcome, err = ctx:detach(2) 92 | ngx.log:revert() 93 | assert.is_false(outcome) 94 | assert.is_same("Token does not match (1 context entries in stack, token provided was 2).", err) 95 | end) 96 | end) 97 | 98 | describe("inject and extract baggage", function() 99 | it("adds baggage to context and extracts it", function() 100 | local ctx = context.new(storage) 101 | local baggage = baggage.new({ key1 = { value = "wat" } }) 102 | local new_ctx = ctx:inject_baggage(baggage) 103 | assert.are.same(new_ctx:extract_baggage(), baggage) 104 | end) 105 | end) 106 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/exporter/circuit.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Contains circuit for use in exporters. For more on the circuit breaker 3 | -- pattern, see https://martinfowler.com/bliki/CircuitBreaker.html. 4 | -------------------------------------------------------------------------------- 5 | local util = require("opentelemetry.util") 6 | local otel_global = require("opentelemetry.global") 7 | 8 | local _M = { 9 | OPEN = "open", 10 | CLOSED = "closed", 11 | HALF_OPEN = "half-open" 12 | } 13 | 14 | local mt = { 15 | __index = _M 16 | } 17 | 18 | -------------------------------------------------------------------------------- 19 | -- Returns a new circuit. No more than 1 request should be in flight at a time, 20 | -- when those requests are brokered by this circuit. 21 | -- 22 | -- @param options Table containing two keys: 23 | -- failure_threshold: number of failures before the circuit opens and requests 24 | -- stop flowing 25 | -- reset_timeout_ms: time in to wait im ms before setting circuit to half-open 26 | -- 27 | -- @return circuit instance 28 | -------------------------------------------------------------------------------- 29 | function _M.new(options) 30 | options = options or {} 31 | local self = { 32 | reset_timeout_ms = options["reset_timeout_ms"] or 5000, 33 | failure_threshold = options["failure_threshold"] or 5, 34 | failure_count = 0, 35 | open_start_time_ms = nil, 36 | state = "closed", 37 | } 38 | return setmetatable(self, mt) 39 | end 40 | 41 | -------------------------------------------------------------------------------- 42 | -- should_make_request determines if a request should be made or not. It assumes 43 | -- that circuit state is either CLOSED or OPEN at beginning of method, since the 44 | -- code assumes that the circuit only brokers one request at a time, and assumes 45 | -- that we only ever make one request in HALF_OPEN state before setting to OPEN 46 | -- or CLOSED. 47 | -- 48 | -- @return boolean 49 | -------------------------------------------------------------------------------- 50 | function _M.should_make_request(self) 51 | if self.state == self.CLOSED then 52 | return true 53 | end 54 | 55 | if self.state == self.OPEN then 56 | if (util.gettimeofday_ms() - self.open_start_time_ms) < self.reset_timeout_ms then 57 | return false 58 | else 59 | self.state = self.HALF_OPEN 60 | return true 61 | end 62 | end 63 | 64 | ngx.log(ngx.ERR, "Circuit breaker could not determine if request should be made (current state: " .. self.state) 65 | end 66 | 67 | -------------------------------------------------------------------------------- 68 | -- record_failure does internal book-keeping about failures and resets circuit 69 | -- state accordingly. 70 | -- 71 | -- @return nil 72 | -------------------------------------------------------------------------------- 73 | function _M.record_failure(self) 74 | self.failure_count = self.failure_count + 1 75 | 76 | if self.state == self.CLOSED and self.failure_count >= self.failure_threshold then 77 | otel_global.metrics_reporter:add_to_counter( 78 | "otel.bsp.circuit_breaker_opened", 1) 79 | self.state = self.OPEN 80 | self.open_start_time_ms = util.gettimeofday_ms() 81 | end 82 | 83 | if self.state == self.HALF_OPEN then 84 | otel_global.metrics_reporter:add_to_counter( 85 | "otel.bsp.circuit_breaker_opened", 1) 86 | self.state = self.OPEN 87 | self.open_start_time_ms = util.gettimeofday_ms() 88 | end 89 | end 90 | 91 | -------------------------------------------------------------------------------- 92 | -- record_success does internal book-keeping about successful requests and 93 | -- resets circuit state accordingly. 94 | -- 95 | -- @return nil 96 | -------------------------------------------------------------------------------- 97 | function _M.record_success(self) 98 | if self.state == self.CLOSED then 99 | return 100 | end 101 | 102 | if self.state == self.HALF_OPEN then 103 | otel_global.metrics_reporter:add_to_counter( 104 | "otel.bsp.circuit_breaker_closed", 1) 105 | self.failure_count = 0 106 | self.state = self.CLOSED 107 | return 108 | end 109 | end 110 | 111 | return _M 112 | -------------------------------------------------------------------------------- /rockspec/opentelemetry-lua-0.2-2.rockspec: -------------------------------------------------------------------------------- 1 | package = "opentelemetry-lua" 2 | version = "0.2-2" 3 | source = { 4 | url = "git://github.com/yangxikun/opentelemetry-lua", 5 | branch = "v0.2.2", 6 | } 7 | 8 | description = { 9 | summary = "The OpenTelemetry Lua SDK. This is repo's default rockspec, which is used to test the HEAD commit.", 10 | homepage = "https://github.com/yangxikun/opentelemetry-lua", 11 | license = "Apache License 2.0" 12 | } 13 | 14 | dependencies = { 15 | "lua-protobuf = 0.3.3", 16 | "lua-resty-http = 0.16.1-0", 17 | } 18 | 19 | build = { 20 | type = "builtin", 21 | modules = { 22 | ["opentelemetry.global"] = "lib/opentelemetry/global.lua", 23 | ["opentelemetry.context"] = "lib/opentelemetry/context.lua", 24 | ["opentelemetry.attribute"] = "lib/opentelemetry/attribute.lua", 25 | ["opentelemetry.instrumentation_library"] = "lib/opentelemetry/instrumentation_library.lua", 26 | ["opentelemetry.resource"] = "lib/opentelemetry/resource.lua", 27 | ["opentelemetry.metrics_reporter"] = "lib/opentelemetry/metrics_reporter.lua", 28 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 29 | ["opentelemetry.trace.event"] = "lib/opentelemetry/trace/event.lua", 30 | ["opentelemetry.trace.exporter.http_client"] = "lib/opentelemetry/trace/exporter/http_client.lua", 31 | ["opentelemetry.trace.exporter.circuit"] = "lib/opentelemetry/trace/exporter/circuit.lua", 32 | ["opentelemetry.trace.exporter.console"] = "lib/opentelemetry/trace/exporter/console.lua", 33 | ["opentelemetry.trace.exporter.encoder"] = "lib/opentelemetry/trace/exporter/encoder.lua", 34 | ["opentelemetry.trace.exporter.otlp"] = "lib/opentelemetry/trace/exporter/otlp.lua", 35 | ["opentelemetry.trace.exporter.pb"] = "lib/opentelemetry/trace/exporter/pb.lua", 36 | ["opentelemetry.trace.id_generator"] = "lib/opentelemetry/trace/id_generator.lua", 37 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 38 | ["opentelemetry.trace.simple_span_processor"] = "lib/opentelemetry/trace/simple_span_processor.lua", 39 | ["opentelemetry.trace.non_recording_span"] = "lib/opentelemetry/trace/non_recording_span.lua", 40 | ["opentelemetry.trace.noop_span"] = "lib/opentelemetry/trace/noop_span.lua", 41 | ["opentelemetry.trace.propagation.text_map.trace_context_propagator"] = "lib/opentelemetry/trace/propagation/text_map/trace_context_propagator.lua", 42 | ["opentelemetry.trace.propagation.text_map.composite_propagator"] = "lib/opentelemetry/trace/propagation/text_map/composite_propagator.lua", 43 | ["opentelemetry.trace.propagation.text_map.getter"] = "lib/opentelemetry/trace/propagation/text_map/getter.lua", 44 | ["opentelemetry.trace.propagation.text_map.setter"] = "lib/opentelemetry/trace/propagation/text_map/setter.lua", 45 | ["opentelemetry.trace.propagation.text_map.noop_propagator"] = "lib/opentelemetry/trace/propagation/text_map/noop_propagator.lua", 46 | ["opentelemetry.trace.recording_span"] = "lib/opentelemetry/trace/recording_span.lua", 47 | ["opentelemetry.trace.sampling.always_off_sampler"] = "lib/opentelemetry/trace/sampling/always_off_sampler.lua", 48 | ["opentelemetry.trace.sampling.always_on_sampler"] = "lib/opentelemetry/trace/sampling/always_on_sampler.lua", 49 | ["opentelemetry.trace.sampling.parent_base_sampler"] = "lib/opentelemetry/trace/sampling/parent_base_sampler.lua", 50 | ["opentelemetry.trace.sampling.result"] = "lib/opentelemetry/trace/sampling/result.lua", 51 | ["opentelemetry.trace.sampling.trace_id_ratio_sampler"] = "lib/opentelemetry/trace/sampling/trace_id_ratio_sampler.lua", 52 | ["opentelemetry.trace.span_context"] = "lib/opentelemetry/trace/span_context.lua", 53 | ["opentelemetry.trace.span_kind"] = "lib/opentelemetry/trace/span_kind.lua", 54 | ["opentelemetry.trace.span_status"] = "lib/opentelemetry/trace/span_status.lua", 55 | ["opentelemetry.trace.tracer"] = "lib/opentelemetry/trace/tracer.lua", 56 | ["opentelemetry.trace.tracer_provider"] = "lib/opentelemetry/trace/tracer_provider.lua", 57 | ["opentelemetry.trace.tracestate"] = "lib/opentelemetry/trace/tracestate.lua", 58 | ["opentelemetry.baggage"] = "lib/opentelemetry/baggage.lua", 59 | ["opentelemetry.baggage.propagation.text_map.baggage_propagator"] = "lib/opentelemetry/baggage/propagation/text_map/baggage_propagator.lua", 60 | ["opentelemetry.util"] = "lib/opentelemetry/util.lua" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/propagation/text_map/trace_context_propagator.lua: -------------------------------------------------------------------------------- 1 | local span_context = require("opentelemetry.trace.span_context") 2 | local tracestate = require("opentelemetry.trace.tracestate") 3 | local text_map_getter = require("opentelemetry.trace.propagation.text_map.getter") 4 | local text_map_setter = require("opentelemetry.trace.propagation.text_map.setter") 5 | local util = require("opentelemetry.util") 6 | 7 | local _M = { 8 | } 9 | 10 | local mt = { 11 | __index = _M, 12 | } 13 | 14 | -- these should be constants, but they were not supported until Lua 5.4, and 15 | -- LuaJIT (which openresty runs on) works up to Lua 5.2 16 | local traceparent_header = "traceparent" 17 | local tracestate_header = "tracestate" 18 | 19 | local invalid_trace_id = span_context.INVALID_TRACE_ID 20 | local invalid_span_id = span_context.INVALID_SPAN_ID 21 | 22 | function _M.new() 23 | return setmetatable( 24 | { 25 | text_map_setter = text_map_setter.new(), 26 | text_map_getter = text_map_getter.new() 27 | }, mt) 28 | end 29 | 30 | ------------------------------------------------------------------ 31 | -- Add tracing information to nginx request as headers 32 | -- 33 | -- @param context context storage 34 | -- @param carrier nginx request 35 | -- @param setter setter for interacting with carrier 36 | -- @return nil 37 | ------------------------------------------------------------------ 38 | function _M:inject(context, carrier, setter) 39 | setter = setter or self.text_map_setter 40 | local span_context = context:span_context() 41 | if not span_context:is_valid() then 42 | return 43 | end 44 | local traceparent = string.format("00-%s-%s-%02x", 45 | span_context.trace_id, span_context.span_id, span_context.trace_flags) 46 | setter.set(carrier, traceparent_header, traceparent) 47 | if span_context.trace_state then 48 | setter.set(carrier, tracestate_header, span_context.trace_state:as_string()) 49 | end 50 | end 51 | 52 | local function validate_trace_id(trace_id) 53 | return type(trace_id) == "string" and #trace_id == 32 and trace_id ~= invalid_trace_id 54 | and string.match(trace_id, "^[0-9a-f]+$") 55 | end 56 | 57 | local function validate_span_id(span_id) 58 | return type(span_id) == "string" and #span_id == 16 and span_id ~= invalid_span_id 59 | and string.match(span_id, "^[0-9a-f]+$") 60 | end 61 | 62 | -- Traceparent: 00-982d663bad6540dece76baf15dd2aa7f-6827812babd449d1-01 63 | -- version-trace-id-parent-id-trace-flags 64 | local function parse_trace_parent(trace_parent) 65 | if type(trace_parent) ~= "string" or trace_parent == "" then 66 | return 67 | end 68 | local ret = util.split(util.trim(trace_parent), "-") 69 | if #ret < 4 then 70 | return 71 | end 72 | 73 | if #ret[1] ~= 2 then 74 | return 75 | end 76 | 77 | local version = tonumber(ret[1], 16) 78 | if not version or version > 254 then 79 | return 80 | end 81 | if version == 0 and #ret ~= 4 then 82 | return 83 | end 84 | 85 | if not validate_trace_id(ret[2]) then 86 | return 87 | end 88 | 89 | if not validate_span_id(ret[3]) then 90 | return 91 | end 92 | 93 | if #ret[4] ~= 2 then 94 | return 95 | end 96 | 97 | local trace_flags = tonumber(ret[4], 16) 98 | if not trace_flags or trace_flags > 2 then 99 | return 100 | end 101 | 102 | return ret[2], ret[3], trace_flags 103 | end 104 | 105 | ------------------------------------------------------------------ 106 | -- extract span context from upstream request. 107 | -- 108 | -- @context current context 109 | -- @carrier get traceparent and tracestate 110 | -- @return new context 111 | ------------------------------------------------------------------ 112 | function _M:extract(context, carrier, getter) 113 | getter = getter or self.text_map_getter 114 | local trace_id, span_id, trace_flags = parse_trace_parent(getter.get(carrier, traceparent_header)) 115 | if not trace_id or not span_id or not trace_flags then 116 | return context 117 | end 118 | 119 | local trace_state = tracestate.parse_tracestate(getter.get(carrier, tracestate_header)) 120 | 121 | return context:with_span_context(span_context.new(trace_id, span_id, trace_flags, trace_state, true)) 122 | end 123 | 124 | function _M.fields() 125 | return { "traceparent", "tracestate" } 126 | end 127 | 128 | return _M 129 | -------------------------------------------------------------------------------- /rockspec/opentelemetry-lua-0.2-3.rockspec: -------------------------------------------------------------------------------- 1 | package = "opentelemetry-lua" 2 | version = "0.2-3" 3 | source = { 4 | url = "git://github.com/yangxikun/opentelemetry-lua", 5 | branch = "v0.2.3", 6 | } 7 | 8 | description = { 9 | summary = "The OpenTelemetry Lua SDK. This is repo's default rockspec, which is used to test the HEAD commit.", 10 | homepage = "https://github.com/yangxikun/opentelemetry-lua", 11 | license = "Apache License 2.0" 12 | } 13 | 14 | dependencies = { 15 | "lua-protobuf = 0.3.3", 16 | "lua-resty-http = 0.16.1-0", 17 | } 18 | 19 | build = { 20 | type = "builtin", 21 | modules = { 22 | ["opentelemetry.api.trace.span_status"] = "lib/opentelemetry/api/trace/span_status.lua", 23 | ["opentelemetry.global"] = "lib/opentelemetry/global.lua", 24 | ["opentelemetry.context"] = "lib/opentelemetry/context.lua", 25 | ["opentelemetry.attribute"] = "lib/opentelemetry/attribute.lua", 26 | ["opentelemetry.instrumentation_library"] = "lib/opentelemetry/instrumentation_library.lua", 27 | ["opentelemetry.resource"] = "lib/opentelemetry/resource.lua", 28 | ["opentelemetry.metrics_reporter"] = "lib/opentelemetry/metrics_reporter.lua", 29 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 30 | ["opentelemetry.trace.event"] = "lib/opentelemetry/trace/event.lua", 31 | ["opentelemetry.trace.exporter.http_client"] = "lib/opentelemetry/trace/exporter/http_client.lua", 32 | ["opentelemetry.trace.exporter.circuit"] = "lib/opentelemetry/trace/exporter/circuit.lua", 33 | ["opentelemetry.trace.exporter.console"] = "lib/opentelemetry/trace/exporter/console.lua", 34 | ["opentelemetry.trace.exporter.encoder"] = "lib/opentelemetry/trace/exporter/encoder.lua", 35 | ["opentelemetry.trace.exporter.otlp"] = "lib/opentelemetry/trace/exporter/otlp.lua", 36 | ["opentelemetry.trace.exporter.pb"] = "lib/opentelemetry/trace/exporter/pb.lua", 37 | ["opentelemetry.trace.id_generator"] = "lib/opentelemetry/trace/id_generator.lua", 38 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 39 | ["opentelemetry.trace.simple_span_processor"] = "lib/opentelemetry/trace/simple_span_processor.lua", 40 | ["opentelemetry.trace.non_recording_span"] = "lib/opentelemetry/trace/non_recording_span.lua", 41 | ["opentelemetry.trace.noop_span"] = "lib/opentelemetry/trace/noop_span.lua", 42 | ["opentelemetry.trace.propagation.text_map.trace_context_propagator"] = "lib/opentelemetry/trace/propagation/text_map/trace_context_propagator.lua", 43 | ["opentelemetry.trace.propagation.text_map.composite_propagator"] = "lib/opentelemetry/trace/propagation/text_map/composite_propagator.lua", 44 | ["opentelemetry.trace.propagation.text_map.getter"] = "lib/opentelemetry/trace/propagation/text_map/getter.lua", 45 | ["opentelemetry.trace.propagation.text_map.setter"] = "lib/opentelemetry/trace/propagation/text_map/setter.lua", 46 | ["opentelemetry.trace.propagation.text_map.noop_propagator"] = "lib/opentelemetry/trace/propagation/text_map/noop_propagator.lua", 47 | ["opentelemetry.trace.recording_span"] = "lib/opentelemetry/trace/recording_span.lua", 48 | ["opentelemetry.trace.sampling.always_off_sampler"] = "lib/opentelemetry/trace/sampling/always_off_sampler.lua", 49 | ["opentelemetry.trace.sampling.always_on_sampler"] = "lib/opentelemetry/trace/sampling/always_on_sampler.lua", 50 | ["opentelemetry.trace.sampling.parent_base_sampler"] = "lib/opentelemetry/trace/sampling/parent_base_sampler.lua", 51 | ["opentelemetry.trace.sampling.result"] = "lib/opentelemetry/trace/sampling/result.lua", 52 | ["opentelemetry.trace.sampling.trace_id_ratio_sampler"] = "lib/opentelemetry/trace/sampling/trace_id_ratio_sampler.lua", 53 | ["opentelemetry.trace.span_context"] = "lib/opentelemetry/trace/span_context.lua", 54 | ["opentelemetry.trace.span_kind"] = "lib/opentelemetry/trace/span_kind.lua", 55 | ["opentelemetry.trace.span_status"] = "lib/opentelemetry/trace/span_status.lua", 56 | ["opentelemetry.trace.tracer"] = "lib/opentelemetry/trace/tracer.lua", 57 | ["opentelemetry.trace.tracer_provider"] = "lib/opentelemetry/trace/tracer_provider.lua", 58 | ["opentelemetry.trace.tracestate"] = "lib/opentelemetry/trace/tracestate.lua", 59 | ["opentelemetry.baggage"] = "lib/opentelemetry/baggage.lua", 60 | ["opentelemetry.baggage.propagation.text_map.baggage_propagator"] = "lib/opentelemetry/baggage/propagation/text_map/baggage_propagator.lua", 61 | ["opentelemetry.util"] = "lib/opentelemetry/util.lua" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/opentelemetry/baggage/propagation/text_map/baggage_propagator.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- 3 | -- See https://w3c.github.io/baggage/ for details. 4 | -- 5 | -------------------------------------------------------------------------------- 6 | 7 | local baggage = require("opentelemetry.baggage") 8 | local text_map_getter = require("opentelemetry.trace.propagation.text_map.getter") 9 | local text_map_setter = require("opentelemetry.trace.propagation.text_map.setter") 10 | local util = require("opentelemetry.util") 11 | 12 | local _M = { 13 | } 14 | 15 | local mt = { 16 | __index = _M, 17 | } 18 | 19 | local baggage_header = "baggage" 20 | 21 | function _M.new() 22 | return setmetatable( 23 | { 24 | text_map_setter = text_map_setter.new(), 25 | text_map_getter = text_map_getter.new() 26 | }, mt) 27 | end 28 | 29 | -------------=------------------------------------------------------------------ 30 | -- Set baggage header on outbound HTTP request header. 31 | -- 32 | -- @param context context storage 33 | -- @param carrier nginx request 34 | -- @param setter setter for interacting with carrier 35 | -- @return nil 36 | -------------------------------------------------------------------------------- 37 | function _M:inject(context, carrier, setter) 38 | setter = setter or self.text_map_setter 39 | local bgg = context:extract_baggage() 40 | local header_string = "" 41 | for k, v in pairs(bgg.values) do 42 | local element = k .. "=" .. v.value 43 | if v.metadata then 44 | element = element .. ";" .. v.metadata 45 | end 46 | element = element .. "," 47 | header_string = header_string .. element 48 | end 49 | 50 | -- trim trailing comma 51 | header_string = header_string:sub(0, -2) 52 | 53 | setter.set( 54 | carrier, 55 | baggage_header, 56 | util.percent_encode_baggage_string(header_string) 57 | ) 58 | end 59 | 60 | -------------------------------------------------------------------------------- 61 | -- Extract baggage from HTTP request headers. 62 | -- 63 | -- @context current context 64 | -- @carrier ngx.req 65 | -- @return new context with baggage associated 66 | -------------------------------------------------------------------------------- 67 | function _M:extract(context, carrier, getter) 68 | getter = getter or self.text_map_getter 69 | local baggage_string = getter.get(carrier, baggage_header) 70 | if not baggage_string then 71 | return context 72 | else 73 | baggage_string = util.decode_percent_encoded_string(baggage_string) 74 | end 75 | 76 | local baggage_entries = {} 77 | -- split apart string on comma and build up baggage entries 78 | for list_member in string.gmatch(baggage_string, "([^,]+)") do 79 | -- extract metadata from each list member 80 | local kv, metadata = string.match(list_member, "([^;]+);(.*)") 81 | 82 | -- If there's no semicolon in list member, then kv and metadata are nil 83 | -- and we need to correct that 84 | if not kv then 85 | kv = list_member 86 | metadata = "" 87 | end 88 | 89 | -- split apart k/v on equals sign 90 | -- and remove leading and trailing whitespaces in k/v 91 | for k, v in string.gmatch(kv, "(%S.-)%s*=%s*(.*%S)") do 92 | if self.validate_baggage(k, v) then 93 | baggage_entries[k] = { value = v, metadata = metadata } 94 | else 95 | ngx.log(ngx.WARN, "invalid baggage entry: " .. k .. "=" .. v) 96 | end 97 | end 98 | end 99 | 100 | local extracted_baggage = baggage.new(baggage_entries) 101 | return context:inject_baggage(extracted_baggage) 102 | end 103 | 104 | -------------------------------------------------------------------------------- 105 | -- Check to see if baggage has both key and value component 106 | -- 107 | -- @key baggage key 108 | -- @value baggage value 109 | -- @return boolean 110 | -------------------------------------------------------------------------------- 111 | function _M.validate_baggage(key, value) 112 | return (key and value) ~= nil 113 | end 114 | 115 | -------------------------------------------------------------------------------- 116 | -- Fields that will be used by the propagator 117 | -- 118 | -- @return table 119 | -------------------------------------------------------------------------------- 120 | function _M.fields() 121 | return { "baggage" } 122 | end 123 | 124 | return _M 125 | -------------------------------------------------------------------------------- /lib/opentelemetry/semantic_conventions/trace/general.lua: -------------------------------------------------------------------------------- 1 | --- This file was automatically generated by utils/generate_semantic_conventions.lua 2 | -- See: https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions 3 | -- 4 | -- module @semantic_conventions.trace.general 5 | local _M = { 6 | -- Transport protocol used. See note below. 7 | NET_TRANSPORT = "net.transport", 8 | -- Application layer protocol used. The value SHOULD be normalized to lowercase. 9 | NET_APP_PROTOCOL_NAME = "net.app.protocol.name", 10 | -- Version of the application layer protocol used. See note below. 11 | NET_APP_PROTOCOL_VERSION = "net.app.protocol.version", 12 | -- Remote socket peer name. 13 | NET_SOCK_PEER_NAME = "net.sock.peer.name", 14 | -- Remote socket peer address: IPv4 or IPv6 for internet protocols, path for local communication, [etc](https://man7.org/linux/man-pages/man7/address_families.7.html). 15 | NET_SOCK_PEER_ADDR = "net.sock.peer.addr", 16 | -- Remote socket peer port. 17 | NET_SOCK_PEER_PORT = "net.sock.peer.port", 18 | -- Protocol [address family](https://man7.org/linux/man-pages/man7/address_families.7.html) which is used for communication. 19 | NET_SOCK_FAMILY = "net.sock.family", 20 | -- Logical remote hostname, see note below. 21 | NET_PEER_NAME = "net.peer.name", 22 | -- Logical remote port number 23 | NET_PEER_PORT = "net.peer.port", 24 | -- Logical local hostname or similar, see note below. 25 | NET_HOST_NAME = "net.host.name", 26 | -- Logical local port number, preferably the one that the peer used to connect 27 | NET_HOST_PORT = "net.host.port", 28 | -- Local socket address. Useful in case of a multi-IP host. 29 | NET_SOCK_HOST_ADDR = "net.sock.host.addr", 30 | -- Local socket port number. 31 | NET_SOCK_HOST_PORT = "net.sock.host.port", 32 | -- The internet connection type currently being used by the host. 33 | NET_HOST_CONNECTION_TYPE = "net.host.connection.type", 34 | -- This describes more details regarding the connection.type. It may be the type of cell technology connection, but it could be used for describing details about a wifi connection. 35 | NET_HOST_CONNECTION_SUBTYPE = "net.host.connection.subtype", 36 | -- The name of the mobile carrier. 37 | NET_HOST_CARRIER_NAME = "net.host.carrier.name", 38 | -- The mobile carrier country code. 39 | NET_HOST_CARRIER_MCC = "net.host.carrier.mcc", 40 | -- The mobile carrier network code. 41 | NET_HOST_CARRIER_MNC = "net.host.carrier.mnc", 42 | -- The ISO 3166-1 alpha-2 2-character country code associated with the mobile carrier network. 43 | NET_HOST_CARRIER_ICC = "net.host.carrier.icc", 44 | -- The [`service.name`](../../resource/semantic_conventions/README.md#service) of the remote service. SHOULD be equal to the actual `service.name` resource attribute of the remote service if any. 45 | PEER_SERVICE = "peer.service", 46 | -- Username or client_id extracted from the access token or [Authorization](https://tools.ietf.org/html/rfc7235#section-4.2) header in the inbound request from outside the system. 47 | ENDUSER_ID = "enduser.id", 48 | -- Actual/assumed role the client is making the request under extracted from token or application security context. 49 | ENDUSER_ROLE = "enduser.role", 50 | -- Scopes or granted authorities the client currently possesses extracted from token or application security context. The value would come from the scope associated with an [OAuth 2.0 Access Token](https://tools.ietf.org/html/rfc6749#section-3.3) or an attribute value in a [SAML 2.0 Assertion](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html). 51 | ENDUSER_SCOPE = "enduser.scope", 52 | -- Current "managed" thread ID (as opposed to OS thread ID). 53 | THREAD_ID = "thread.id", 54 | -- Current thread name. 55 | THREAD_NAME = "thread.name", 56 | -- The method or function name, or equivalent (usually rightmost part of the code unit's name). 57 | CODE_FUNCTION = "code.function", 58 | -- The "namespace" within which `code.function` is defined. Usually the qualified class or module name, such that `code.namespace` + some separator + `code.function` form a unique identifier for the code unit. 59 | CODE_NAMESPACE = "code.namespace", 60 | -- The source code file name that identifies the code unit as uniquely as possible (preferably an absolute file path). 61 | CODE_FILEPATH = "code.filepath", 62 | -- The line number in `code.filepath` best representing the operation. It SHOULD point within the code unit named in `code.function`. 63 | CODE_LINENO = "code.lineno", 64 | -- The column number in `code.filepath` best representing the operation. It SHOULD point within the code unit named in `code.function`. 65 | CODE_COLUMN = "code.column" 66 | } 67 | return _M 68 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/recording_span.lua: -------------------------------------------------------------------------------- 1 | local attribute = require("opentelemetry.attribute") 2 | local event_new = require("opentelemetry.trace.event").new 3 | local span_kind = require("opentelemetry.trace.span_kind") 4 | local span_status = require("opentelemetry.trace.span_status") 5 | local util = require("opentelemetry.util") 6 | 7 | local _M = { 8 | } 9 | 10 | local mt = { 11 | __index = _M 12 | } 13 | 14 | ------------------------------------------------------------------ 15 | -- create a recording span. 16 | -- 17 | -- @tracer 18 | -- @parent_ctx parent span context 19 | -- @ctx current span context 20 | -- @name span name 21 | -- @config optional config 22 | -- config.kind: span kind 23 | -- config.attributes: span attributes 24 | -- @start_time optional start time 25 | -- @return span 26 | ------------------------------------------------------------------ 27 | function _M.new(tracer, parent_ctx, ctx, name, config, start_time) 28 | local self = { 29 | tracer = tracer, 30 | parent_ctx = parent_ctx, 31 | ctx = ctx, 32 | name = name, 33 | start_time = start_time or util.time_nano(), 34 | end_time = 0, 35 | kind = span_kind.validate(config.kind), 36 | attributes = config.attributes or {}, 37 | events = {}, 38 | } 39 | return setmetatable(self, mt) 40 | end 41 | 42 | function _M.context(self) 43 | return self.ctx 44 | end 45 | 46 | function _M.is_recording(self) 47 | return self.end_time == 0 48 | end 49 | 50 | ------------------------------------------------------------------ 51 | -- set span status. 52 | -- 53 | -- @code see opentelemetry.trace.span_status.* 54 | -- @message error msg 55 | ------------------------------------------------------------------ 56 | function _M.set_status(self, code, message) 57 | if not self:is_recording() then 58 | return 59 | end 60 | 61 | code = span_status.validate(code) 62 | local status = { 63 | code = code, 64 | message = "" 65 | } 66 | if code == span_status.ERROR then 67 | status.message = message 68 | end 69 | 70 | self.status = status 71 | end 72 | 73 | function _M.set_attributes(self, ...) 74 | if not self:is_recording() then 75 | return 76 | end 77 | 78 | for _, attr in ipairs({ ... }) do 79 | table.insert(self.attributes, attr) 80 | end 81 | end 82 | 83 | ------------------------------------------------------------------ 84 | -- Finish the span. `end` is key word, so we use "finish." 85 | -- See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#end 86 | -- 87 | -- @end_timestamp end timestamp for span (nanoseconds). If not 88 | -- supplied, span method will end at current 89 | -- time. 90 | ------------------------------------------------------------------ 91 | function _M.finish(self, end_timestamp) 92 | if not self:is_recording() then 93 | return 94 | end 95 | 96 | self.end_time = end_timestamp or util.time_nano() 97 | for _, sp in ipairs(self.tracer.provider.span_processors) do 98 | sp:on_end(self) 99 | end 100 | end 101 | 102 | ------------------------------------------------------------------ 103 | -- add a error event 104 | -- 105 | -- @error a string describe the error 106 | ------------------------------------------------------------------ 107 | function _M.record_error(self, error) 108 | if error == nil or (type(error) ~= "string") or (not self:is_recording()) then 109 | return 110 | end 111 | 112 | table.insert(self.events, event_new("exception", { 113 | attributes = { attribute.string("exception.message", error) }, 114 | })) 115 | end 116 | 117 | ------------------------------------------------------------------ 118 | -- add a custom event 119 | -- 120 | -- @opts [optional] 121 | -- opts.attributes: a list of attribute 122 | ------------------------------------------------------------------ 123 | function _M.add_event(self, name, opts) 124 | if not self:is_recording() then 125 | return 126 | end 127 | 128 | table.insert(self.events, event_new(name, { 129 | attributes = opts.attributes, 130 | })) 131 | end 132 | 133 | ------------------------------------------------------------------ 134 | -- update span name 135 | ------------------------------------------------------------------ 136 | function _M.set_name(self, name) 137 | self.name = name 138 | end 139 | 140 | function _M.tracer_provider(self) 141 | return self.tracer.provider 142 | end 143 | 144 | function _M.plain(self) 145 | return { 146 | tracer = { il = self.tracer.il }, 147 | parent_ctx = self.parent_ctx:plain(), 148 | ctx = self.ctx:plain(), 149 | name = self.name, 150 | start_time = self.start_time, 151 | end_time = self.end_time, 152 | kind = self.kind, 153 | attributes = self.attributes, 154 | events = self.events, 155 | } 156 | end 157 | 158 | return _M 159 | -------------------------------------------------------------------------------- /lib/opentelemetry/context.lua: -------------------------------------------------------------------------------- 1 | local baggage = require("opentelemetry.baggage") 2 | local otel_global = require("opentelemetry.global") 3 | local non_recording_span_new = require("opentelemetry.trace.non_recording_span").new 4 | local noop_span = require("opentelemetry.trace.noop_span") 5 | local util = require("opentelemetry.util") 6 | 7 | local _M = { 8 | } 9 | 10 | local mt = { 11 | __index = _M 12 | } 13 | 14 | local context_key = "__opentelemetry_context__" 15 | local baggage_context_key = "__opentelemetry_baggage__" 16 | 17 | -------------------------------------------------------------------------------- 18 | -- Create new context with set of entries 19 | -- 20 | -- @return context 21 | -------------------------------------------------------------------------------- 22 | function _M.new(entries, span) 23 | return setmetatable({ sp = span or noop_span.new(), entries = entries or {} }, mt) 24 | end 25 | 26 | -------------------------------------------------------------------------------- 27 | -- Set this context as current by pushing it on stack stored at context_key. 28 | -- 29 | -- @return token to be used for detaching 30 | -------------------------------------------------------------------------------- 31 | function _M.attach(self) 32 | if otel_global.get_context_storage()[context_key] then 33 | table.insert(otel_global.get_context_storage()[context_key], self) 34 | else 35 | otel_global.get_context_storage()[context_key] = { self } 36 | end 37 | 38 | -- the length of the stack is token used to detach context 39 | return #otel_global.get_context_storage()[context_key] 40 | end 41 | 42 | -------------------------------------------------------------------------------- 43 | -- Detach current context, setting current context to previous element in stack 44 | -- If token does not match length of elements in stack, returns false and error 45 | -- string. 46 | -- 47 | -- @return boolean, string 48 | -------------------------------------------------------------------------------- 49 | function _M.detach(self, token) 50 | if #otel_global.get_context_storage()[context_key] == token then 51 | table.remove(otel_global.get_context_storage()[context_key]) 52 | return true, nil 53 | else 54 | local error_message = "Token does not match (" .. 55 | #otel_global.get_context_storage()[context_key] .. 56 | " context entries in stack, token provided was " .. token .. ")." 57 | ngx.log(ngx.WARN, error_message) 58 | return false, error_message 59 | end 60 | end 61 | 62 | -------------------------------------------------------------------------------- 63 | -- Get current context, which is the final element in stack stored at 64 | -- context_key. 65 | -- 66 | -- @return boolean, string 67 | -------------------------------------------------------------------------------- 68 | function _M.current() 69 | if otel_global.get_context_storage()[context_key] then 70 | return otel_global.get_context_storage()[context_key][#otel_global.get_context_storage()[context_key]] 71 | else 72 | return _M.new() 73 | end 74 | end 75 | 76 | -------------------------------------------------------------------------------- 77 | -- Retrieve value for key in context. 78 | -- 79 | -- @key key for which to set the value in context 80 | -- @return value stored at key 81 | -------------------------------------------------------------------------------- 82 | function _M.get(self, key) 83 | return self.entries[key] 84 | end 85 | 86 | -------------------------------------------------------------------------------- 87 | -- Set value for key in context. This returns a new context object. 88 | -- 89 | -- @key key for which to set the value in context 90 | -- @value value to set 91 | -- @return context 92 | -------------------------------------------------------------------------------- 93 | function _M.set(self, key, value) 94 | local vals = util.shallow_copy_table(self.entries) 95 | vals[key] = value 96 | return self.new(vals, self.sp) 97 | end 98 | 99 | -------------------------------------------------------------------------------- 100 | -- Inject baggage into current context 101 | -- 102 | -- @baggage baggage instance to inject 103 | -- @return context 104 | -------------------------------------------------------------------------------- 105 | function _M.inject_baggage(self, baggage) 106 | return self:set(baggage_context_key, baggage) 107 | end 108 | 109 | -------------------------------------------------------------------------------- 110 | -- Extract baggage from context 111 | -- 112 | -- @return baggage 113 | -------------------------------------------------------------------------------- 114 | function _M.extract_baggage(self) 115 | return self:get(baggage_context_key) or baggage.new({}) 116 | end 117 | 118 | function _M.with_span(self, span) 119 | return self.new(util.shallow_copy_table(self.entries or {}), span) 120 | end 121 | 122 | function _M.with_span_context(self, span_context) 123 | return self:with_span(non_recording_span_new(nil, span_context)) 124 | end 125 | 126 | function _M.span_context(self) 127 | return self.sp:context() 128 | end 129 | 130 | function _M.span(self) 131 | return self.sp 132 | end 133 | 134 | return _M 135 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/tracestate.lua: -------------------------------------------------------------------------------- 1 | local _M = { 2 | MAX_KEY_LEN = 256, 3 | MAX_VAL_LEN = 256, 4 | MAX_ENTRIES = 32, 5 | } 6 | 7 | local mt = { 8 | __index = _M 9 | } 10 | 11 | local function validate_member_key(key) 12 | if #key > _M.MAX_KEY_LEN then 13 | return nil 14 | end 15 | 16 | local valid_key = string.match(key, [[^%s*([a-z][_0-9a-z%-*/]*)$]]) 17 | if not valid_key then 18 | local tenant_id, system_id = string.match(key, [[^%s*([a-z0-9][_0-9a-z%-*/]*)@([a-z][_0-9a-z%-*/]*)$]]) 19 | if not tenant_id or not system_id then 20 | return nil 21 | end 22 | if #tenant_id > 241 or #system_id > 14 then 23 | return nil 24 | end 25 | return tenant_id .. "@" .. system_id 26 | end 27 | 28 | return valid_key 29 | end 30 | 31 | local function validate_member_value(value) 32 | if #value > _M.MAX_VAL_LEN then 33 | return nil 34 | end 35 | return string.match(value, 36 | [[^([ !"#$%%&'()*+%-./0-9:;<>?@A-Z[\%]^_`a-z{|}~]*[!"#$%%&'()*+%-./0-9:;<>?@A-Z[\%]^_`a-z{|}~])%s*$]]) 37 | end 38 | 39 | function _M.new(values) 40 | local self = { values = values } 41 | return setmetatable(self, mt) 42 | end 43 | 44 | -------------------------------------------------------------------------------- 45 | -- Parse tracestate header into a tracestate 46 | -- 47 | -- @return tracestate 48 | -------------------------------------------------------------------------------- 49 | function _M.parse_tracestate(tracestate) 50 | if not tracestate then 51 | return _M.new({}) 52 | end 53 | -- if there is only one tracestate header it comes in as a string, otherwise its a table 54 | if type(tracestate) == "string" then 55 | tracestate = { tracestate } 56 | end 57 | 58 | local new_tracestate = {} 59 | local members_count = 0 60 | local error_message = "failed to parse tracestate" 61 | for _, item in ipairs(tracestate) do 62 | for member in string.gmatch(item, "([^,]+)") do 63 | if member ~= "" then 64 | local start_pos, end_pos = string.find(member, "=", 1, true) 65 | if not start_pos or start_pos == 1 then 66 | ngx.log(ngx.WARN, error_message) 67 | return _M.new({}) 68 | end 69 | local key = validate_member_key(string.sub(member, 1, start_pos - 1)) 70 | if not key then 71 | ngx.log(ngx.WARN, error_message) 72 | return _M.new({}) 73 | end 74 | local value = validate_member_value(string.sub(member, end_pos + 1)) 75 | if not value then 76 | ngx.log(ngx.WARN, error_message) 77 | return _M.new({}) 78 | end 79 | members_count = members_count + 1 80 | if members_count > _M.MAX_ENTRIES then 81 | ngx.log(ngx.WARN, error_message) 82 | return _M.new({}) 83 | end 84 | table.insert(new_tracestate, {key, value}) 85 | end 86 | end 87 | end 88 | 89 | return _M.new(new_tracestate) 90 | end 91 | 92 | -------------------------------------------------------------------------------- 93 | -- Set the key value pair for the tracestate 94 | -- 95 | -- @return tracestate 96 | -------------------------------------------------------------------------------- 97 | function _M.set(self, key, value) 98 | if not validate_member_key(key) then 99 | return self 100 | end 101 | if not validate_member_value(value) then 102 | return self 103 | end 104 | self:del(key) 105 | if #self.values >= _M.MAX_ENTRIES then 106 | table.remove(self.values) 107 | ngx.log(ngx.WARN, "tracestate max values exceeded, removing rightmost entry") 108 | end 109 | table.insert(self.values, 1, {key, value}) 110 | return self 111 | end 112 | 113 | -------------------------------------------------------------------------------- 114 | -- Get the value for the current key from the tracestate 115 | -- 116 | -- @return value 117 | -------------------------------------------------------------------------------- 118 | function _M.get(self, key) 119 | for _, item in ipairs(self.values) do 120 | local ckey = item[1] 121 | if ckey == key then 122 | return item[2] 123 | end 124 | end 125 | return "" 126 | end 127 | 128 | -------------------------------------------------------------------------------- 129 | -- Delete the key from the tracestate 130 | -- 131 | -- @return tracestate 132 | -------------------------------------------------------------------------------- 133 | function _M.del(self, key) 134 | local index = 0 135 | for i, item in ipairs(self.values) do 136 | local ckey = item[1] 137 | if ckey == key then 138 | index = i 139 | break 140 | end 141 | end 142 | if index ~= 0 then 143 | table.remove(self.values, index) 144 | end 145 | return self 146 | end 147 | 148 | -------------------------------------------------------------------------------- 149 | -- Return the header value of the tracestate 150 | -- 151 | -- @return string 152 | -------------------------------------------------------------------------------- 153 | function _M.as_string(self) 154 | local output = {} 155 | for _, item in ipairs(self.values) do 156 | table.insert(output, item[1] .. "=" .. item[2]) 157 | end 158 | return table.concat(output, ",") 159 | end 160 | 161 | return _M 162 | -------------------------------------------------------------------------------- /rockspec/opentelemetry-lua-0.2-4.rockspec: -------------------------------------------------------------------------------- 1 | package = "opentelemetry-lua" 2 | version = "0.2-4" 3 | source = { 4 | url = "git://github.com/yangxikun/opentelemetry-lua", 5 | branch = "v0.2.4", 6 | } 7 | 8 | description = { 9 | summary = "The OpenTelemetry Lua SDK. This is repo's default rockspec, which is used to test the HEAD commit.", 10 | homepage = "https://github.com/yangxikun/opentelemetry-lua", 11 | license = "Apache License 2.0" 12 | } 13 | 14 | dependencies = { 15 | "lua-protobuf = 0.3.3", 16 | "lua-resty-http = 0.16.1-0", 17 | } 18 | 19 | build = { 20 | type = "builtin", 21 | modules = { 22 | ["opentelemetry.api.trace.span_status"] = "lib/opentelemetry/api/trace/span_status.lua", 23 | ["opentelemetry.global"] = "lib/opentelemetry/global.lua", 24 | ["opentelemetry.context"] = "lib/opentelemetry/context.lua", 25 | ["opentelemetry.attribute"] = "lib/opentelemetry/attribute.lua", 26 | ["opentelemetry.instrumentation_library"] = "lib/opentelemetry/instrumentation_library.lua", 27 | ["opentelemetry.resource"] = "lib/opentelemetry/resource.lua", 28 | ["opentelemetry.metrics_reporter"] = "lib/opentelemetry/metrics_reporter.lua", 29 | ["opentelemetry.semantic_conventions.trace.aws"] = "lib/opentelemetry/semantic_conventions/trace/aws.lua", 30 | ["opentelemetry.semantic_conventions.trace.cloudevents"] = "lib/opentelemetry/semantic_conventions/trace/cloudevents.lua", 31 | ["opentelemetry.semantic_conventions.trace.compatibility"] = "lib/opentelemetry/semantic_conventions/trace/compatibility.lua", 32 | ["opentelemetry.semantic_conventions.trace.database"] = "lib/opentelemetry/semantic_conventions/trace/database.lua", 33 | ["opentelemetry.semantic_conventions.trace.exporter"] = "lib/opentelemetry/semantic_conventions/trace/exporter.lua", 34 | ["opentelemetry.semantic_conventions.trace.faas"] = "lib/opentelemetry/semantic_conventions/trace/faas.lua", 35 | ["opentelemetry.semantic_conventions.trace.feature"] = "lib/opentelemetry/semantic_conventions/trace/feature.lua", 36 | ["opentelemetry.semantic_conventions.trace.general"] = "lib/opentelemetry/semantic_conventions/trace/general.lua", 37 | ["opentelemetry.semantic_conventions.trace.http"] = "lib/opentelemetry/semantic_conventions/trace/http.lua", 38 | ["opentelemetry.semantic_conventions.trace.messaging"] = "lib/opentelemetry/semantic_conventions/trace/messaging.lua", 39 | ["opentelemetry.semantic_conventions.trace.rpc"] = "lib/opentelemetry/semantic_conventions/trace/rpc.lua", 40 | ["opentelemetry.semantic_conventions.trace.trace"] = "lib/opentelemetry/semantic_conventions/trace/trace.lua", 41 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 42 | ["opentelemetry.trace.event"] = "lib/opentelemetry/trace/event.lua", 43 | ["opentelemetry.trace.exporter.http_client"] = "lib/opentelemetry/trace/exporter/http_client.lua", 44 | ["opentelemetry.trace.exporter.circuit"] = "lib/opentelemetry/trace/exporter/circuit.lua", 45 | ["opentelemetry.trace.exporter.console"] = "lib/opentelemetry/trace/exporter/console.lua", 46 | ["opentelemetry.trace.exporter.encoder"] = "lib/opentelemetry/trace/exporter/encoder.lua", 47 | ["opentelemetry.trace.exporter.otlp"] = "lib/opentelemetry/trace/exporter/otlp.lua", 48 | ["opentelemetry.trace.exporter.pb"] = "lib/opentelemetry/trace/exporter/pb.lua", 49 | ["opentelemetry.trace.id_generator"] = "lib/opentelemetry/trace/id_generator.lua", 50 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 51 | ["opentelemetry.trace.simple_span_processor"] = "lib/opentelemetry/trace/simple_span_processor.lua", 52 | ["opentelemetry.trace.non_recording_span"] = "lib/opentelemetry/trace/non_recording_span.lua", 53 | ["opentelemetry.trace.noop_span"] = "lib/opentelemetry/trace/noop_span.lua", 54 | ["opentelemetry.trace.propagation.text_map.trace_context_propagator"] = "lib/opentelemetry/trace/propagation/text_map/trace_context_propagator.lua", 55 | ["opentelemetry.trace.propagation.text_map.composite_propagator"] = "lib/opentelemetry/trace/propagation/text_map/composite_propagator.lua", 56 | ["opentelemetry.trace.propagation.text_map.getter"] = "lib/opentelemetry/trace/propagation/text_map/getter.lua", 57 | ["opentelemetry.trace.propagation.text_map.setter"] = "lib/opentelemetry/trace/propagation/text_map/setter.lua", 58 | ["opentelemetry.trace.propagation.text_map.noop_propagator"] = "lib/opentelemetry/trace/propagation/text_map/noop_propagator.lua", 59 | ["opentelemetry.trace.recording_span"] = "lib/opentelemetry/trace/recording_span.lua", 60 | ["opentelemetry.trace.sampling.always_off_sampler"] = "lib/opentelemetry/trace/sampling/always_off_sampler.lua", 61 | ["opentelemetry.trace.sampling.always_on_sampler"] = "lib/opentelemetry/trace/sampling/always_on_sampler.lua", 62 | ["opentelemetry.trace.sampling.parent_base_sampler"] = "lib/opentelemetry/trace/sampling/parent_base_sampler.lua", 63 | ["opentelemetry.trace.sampling.result"] = "lib/opentelemetry/trace/sampling/result.lua", 64 | ["opentelemetry.trace.sampling.trace_id_ratio_sampler"] = "lib/opentelemetry/trace/sampling/trace_id_ratio_sampler.lua", 65 | ["opentelemetry.trace.span_context"] = "lib/opentelemetry/trace/span_context.lua", 66 | ["opentelemetry.trace.span_kind"] = "lib/opentelemetry/trace/span_kind.lua", 67 | ["opentelemetry.trace.span_status"] = "lib/opentelemetry/trace/span_status.lua", 68 | ["opentelemetry.trace.tracer"] = "lib/opentelemetry/trace/tracer.lua", 69 | ["opentelemetry.trace.tracer_provider"] = "lib/opentelemetry/trace/tracer_provider.lua", 70 | ["opentelemetry.trace.tracestate"] = "lib/opentelemetry/trace/tracestate.lua", 71 | ["opentelemetry.baggage"] = "lib/opentelemetry/baggage.lua", 72 | ["opentelemetry.baggage.propagation.text_map.baggage_propagator"] = "lib/opentelemetry/baggage/propagation/text_map/baggage_propagator.lua", 73 | ["opentelemetry.util"] = "lib/opentelemetry/util.lua" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /rockspec/opentelemetry-lua-0.2-5.rockspec: -------------------------------------------------------------------------------- 1 | package = "opentelemetry-lua" 2 | version = "0.2-5" 3 | source = { 4 | url = "git://github.com/yangxikun/opentelemetry-lua", 5 | branch = "v0.2.5", 6 | } 7 | 8 | description = { 9 | summary = "The OpenTelemetry Lua SDK. This is repo's default rockspec, which is used to test the HEAD commit.", 10 | homepage = "https://github.com/yangxikun/opentelemetry-lua", 11 | license = "Apache License 2.0" 12 | } 13 | 14 | dependencies = { 15 | "lua-protobuf = 0.3.3", 16 | "lua-resty-http = 0.16.1-0", 17 | } 18 | 19 | build = { 20 | type = "builtin", 21 | modules = { 22 | ["opentelemetry.api.trace.span_status"] = "lib/opentelemetry/api/trace/span_status.lua", 23 | ["opentelemetry.global"] = "lib/opentelemetry/global.lua", 24 | ["opentelemetry.context"] = "lib/opentelemetry/context.lua", 25 | ["opentelemetry.attribute"] = "lib/opentelemetry/attribute.lua", 26 | ["opentelemetry.instrumentation_library"] = "lib/opentelemetry/instrumentation_library.lua", 27 | ["opentelemetry.resource"] = "lib/opentelemetry/resource.lua", 28 | ["opentelemetry.metrics_reporter"] = "lib/opentelemetry/metrics_reporter.lua", 29 | ["opentelemetry.semantic_conventions.trace.aws"] = "lib/opentelemetry/semantic_conventions/trace/aws.lua", 30 | ["opentelemetry.semantic_conventions.trace.cloudevents"] = "lib/opentelemetry/semantic_conventions/trace/cloudevents.lua", 31 | ["opentelemetry.semantic_conventions.trace.compatibility"] = "lib/opentelemetry/semantic_conventions/trace/compatibility.lua", 32 | ["opentelemetry.semantic_conventions.trace.database"] = "lib/opentelemetry/semantic_conventions/trace/database.lua", 33 | ["opentelemetry.semantic_conventions.trace.exporter"] = "lib/opentelemetry/semantic_conventions/trace/exporter.lua", 34 | ["opentelemetry.semantic_conventions.trace.faas"] = "lib/opentelemetry/semantic_conventions/trace/faas.lua", 35 | ["opentelemetry.semantic_conventions.trace.feature"] = "lib/opentelemetry/semantic_conventions/trace/feature.lua", 36 | ["opentelemetry.semantic_conventions.trace.general"] = "lib/opentelemetry/semantic_conventions/trace/general.lua", 37 | ["opentelemetry.semantic_conventions.trace.http"] = "lib/opentelemetry/semantic_conventions/trace/http.lua", 38 | ["opentelemetry.semantic_conventions.trace.messaging"] = "lib/opentelemetry/semantic_conventions/trace/messaging.lua", 39 | ["opentelemetry.semantic_conventions.trace.rpc"] = "lib/opentelemetry/semantic_conventions/trace/rpc.lua", 40 | ["opentelemetry.semantic_conventions.trace.trace"] = "lib/opentelemetry/semantic_conventions/trace/trace.lua", 41 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 42 | ["opentelemetry.trace.event"] = "lib/opentelemetry/trace/event.lua", 43 | ["opentelemetry.trace.exporter.http_client"] = "lib/opentelemetry/trace/exporter/http_client.lua", 44 | ["opentelemetry.trace.exporter.circuit"] = "lib/opentelemetry/trace/exporter/circuit.lua", 45 | ["opentelemetry.trace.exporter.console"] = "lib/opentelemetry/trace/exporter/console.lua", 46 | ["opentelemetry.trace.exporter.encoder"] = "lib/opentelemetry/trace/exporter/encoder.lua", 47 | ["opentelemetry.trace.exporter.otlp"] = "lib/opentelemetry/trace/exporter/otlp.lua", 48 | ["opentelemetry.trace.exporter.pb"] = "lib/opentelemetry/trace/exporter/pb.lua", 49 | ["opentelemetry.trace.id_generator"] = "lib/opentelemetry/trace/id_generator.lua", 50 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 51 | ["opentelemetry.trace.simple_span_processor"] = "lib/opentelemetry/trace/simple_span_processor.lua", 52 | ["opentelemetry.trace.non_recording_span"] = "lib/opentelemetry/trace/non_recording_span.lua", 53 | ["opentelemetry.trace.noop_span"] = "lib/opentelemetry/trace/noop_span.lua", 54 | ["opentelemetry.trace.propagation.text_map.trace_context_propagator"] = "lib/opentelemetry/trace/propagation/text_map/trace_context_propagator.lua", 55 | ["opentelemetry.trace.propagation.text_map.composite_propagator"] = "lib/opentelemetry/trace/propagation/text_map/composite_propagator.lua", 56 | ["opentelemetry.trace.propagation.text_map.getter"] = "lib/opentelemetry/trace/propagation/text_map/getter.lua", 57 | ["opentelemetry.trace.propagation.text_map.setter"] = "lib/opentelemetry/trace/propagation/text_map/setter.lua", 58 | ["opentelemetry.trace.propagation.text_map.noop_propagator"] = "lib/opentelemetry/trace/propagation/text_map/noop_propagator.lua", 59 | ["opentelemetry.trace.recording_span"] = "lib/opentelemetry/trace/recording_span.lua", 60 | ["opentelemetry.trace.sampling.always_off_sampler"] = "lib/opentelemetry/trace/sampling/always_off_sampler.lua", 61 | ["opentelemetry.trace.sampling.always_on_sampler"] = "lib/opentelemetry/trace/sampling/always_on_sampler.lua", 62 | ["opentelemetry.trace.sampling.parent_base_sampler"] = "lib/opentelemetry/trace/sampling/parent_base_sampler.lua", 63 | ["opentelemetry.trace.sampling.result"] = "lib/opentelemetry/trace/sampling/result.lua", 64 | ["opentelemetry.trace.sampling.trace_id_ratio_sampler"] = "lib/opentelemetry/trace/sampling/trace_id_ratio_sampler.lua", 65 | ["opentelemetry.trace.span_context"] = "lib/opentelemetry/trace/span_context.lua", 66 | ["opentelemetry.trace.span_kind"] = "lib/opentelemetry/trace/span_kind.lua", 67 | ["opentelemetry.trace.span_status"] = "lib/opentelemetry/trace/span_status.lua", 68 | ["opentelemetry.trace.tracer"] = "lib/opentelemetry/trace/tracer.lua", 69 | ["opentelemetry.trace.tracer_provider"] = "lib/opentelemetry/trace/tracer_provider.lua", 70 | ["opentelemetry.trace.tracestate"] = "lib/opentelemetry/trace/tracestate.lua", 71 | ["opentelemetry.baggage"] = "lib/opentelemetry/baggage.lua", 72 | ["opentelemetry.baggage.propagation.text_map.baggage_propagator"] = "lib/opentelemetry/baggage/propagation/text_map/baggage_propagator.lua", 73 | ["opentelemetry.util"] = "lib/opentelemetry/util.lua" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /rockspec/opentelemetry-lua-0.2-6.rockspec: -------------------------------------------------------------------------------- 1 | package = "opentelemetry-lua" 2 | version = "0.2-6" 3 | source = { 4 | url = "git://github.com/yangxikun/opentelemetry-lua", 5 | branch = "v0.2.6", 6 | } 7 | 8 | description = { 9 | summary = "The OpenTelemetry Lua SDK. This is repo's default rockspec, which is used to test the HEAD commit.", 10 | homepage = "https://github.com/yangxikun/opentelemetry-lua", 11 | license = "Apache License 2.0" 12 | } 13 | 14 | dependencies = { 15 | "lua-protobuf = 0.3.3", 16 | "lua-resty-http = 0.16.1-0", 17 | } 18 | 19 | build = { 20 | type = "builtin", 21 | modules = { 22 | ["opentelemetry.api.trace.span_status"] = "lib/opentelemetry/api/trace/span_status.lua", 23 | ["opentelemetry.global"] = "lib/opentelemetry/global.lua", 24 | ["opentelemetry.context"] = "lib/opentelemetry/context.lua", 25 | ["opentelemetry.attribute"] = "lib/opentelemetry/attribute.lua", 26 | ["opentelemetry.instrumentation_library"] = "lib/opentelemetry/instrumentation_library.lua", 27 | ["opentelemetry.resource"] = "lib/opentelemetry/resource.lua", 28 | ["opentelemetry.metrics_reporter"] = "lib/opentelemetry/metrics_reporter.lua", 29 | ["opentelemetry.semantic_conventions.trace.aws"] = "lib/opentelemetry/semantic_conventions/trace/aws.lua", 30 | ["opentelemetry.semantic_conventions.trace.cloudevents"] = "lib/opentelemetry/semantic_conventions/trace/cloudevents.lua", 31 | ["opentelemetry.semantic_conventions.trace.compatibility"] = "lib/opentelemetry/semantic_conventions/trace/compatibility.lua", 32 | ["opentelemetry.semantic_conventions.trace.database"] = "lib/opentelemetry/semantic_conventions/trace/database.lua", 33 | ["opentelemetry.semantic_conventions.trace.exporter"] = "lib/opentelemetry/semantic_conventions/trace/exporter.lua", 34 | ["opentelemetry.semantic_conventions.trace.faas"] = "lib/opentelemetry/semantic_conventions/trace/faas.lua", 35 | ["opentelemetry.semantic_conventions.trace.feature"] = "lib/opentelemetry/semantic_conventions/trace/feature.lua", 36 | ["opentelemetry.semantic_conventions.trace.general"] = "lib/opentelemetry/semantic_conventions/trace/general.lua", 37 | ["opentelemetry.semantic_conventions.trace.http"] = "lib/opentelemetry/semantic_conventions/trace/http.lua", 38 | ["opentelemetry.semantic_conventions.trace.messaging"] = "lib/opentelemetry/semantic_conventions/trace/messaging.lua", 39 | ["opentelemetry.semantic_conventions.trace.rpc"] = "lib/opentelemetry/semantic_conventions/trace/rpc.lua", 40 | ["opentelemetry.semantic_conventions.trace.trace"] = "lib/opentelemetry/semantic_conventions/trace/trace.lua", 41 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 42 | ["opentelemetry.trace.event"] = "lib/opentelemetry/trace/event.lua", 43 | ["opentelemetry.trace.exporter.http_client"] = "lib/opentelemetry/trace/exporter/http_client.lua", 44 | ["opentelemetry.trace.exporter.circuit"] = "lib/opentelemetry/trace/exporter/circuit.lua", 45 | ["opentelemetry.trace.exporter.console"] = "lib/opentelemetry/trace/exporter/console.lua", 46 | ["opentelemetry.trace.exporter.encoder"] = "lib/opentelemetry/trace/exporter/encoder.lua", 47 | ["opentelemetry.trace.exporter.otlp"] = "lib/opentelemetry/trace/exporter/otlp.lua", 48 | ["opentelemetry.trace.exporter.pb"] = "lib/opentelemetry/trace/exporter/pb.lua", 49 | ["opentelemetry.trace.id_generator"] = "lib/opentelemetry/trace/id_generator.lua", 50 | ["opentelemetry.trace.batch_span_processor"] = "lib/opentelemetry/trace/batch_span_processor.lua", 51 | ["opentelemetry.trace.simple_span_processor"] = "lib/opentelemetry/trace/simple_span_processor.lua", 52 | ["opentelemetry.trace.non_recording_span"] = "lib/opentelemetry/trace/non_recording_span.lua", 53 | ["opentelemetry.trace.noop_span"] = "lib/opentelemetry/trace/noop_span.lua", 54 | ["opentelemetry.trace.propagation.text_map.trace_context_propagator"] = "lib/opentelemetry/trace/propagation/text_map/trace_context_propagator.lua", 55 | ["opentelemetry.trace.propagation.text_map.composite_propagator"] = "lib/opentelemetry/trace/propagation/text_map/composite_propagator.lua", 56 | ["opentelemetry.trace.propagation.text_map.getter"] = "lib/opentelemetry/trace/propagation/text_map/getter.lua", 57 | ["opentelemetry.trace.propagation.text_map.setter"] = "lib/opentelemetry/trace/propagation/text_map/setter.lua", 58 | ["opentelemetry.trace.propagation.text_map.noop_propagator"] = "lib/opentelemetry/trace/propagation/text_map/noop_propagator.lua", 59 | ["opentelemetry.trace.recording_span"] = "lib/opentelemetry/trace/recording_span.lua", 60 | ["opentelemetry.trace.sampling.always_off_sampler"] = "lib/opentelemetry/trace/sampling/always_off_sampler.lua", 61 | ["opentelemetry.trace.sampling.always_on_sampler"] = "lib/opentelemetry/trace/sampling/always_on_sampler.lua", 62 | ["opentelemetry.trace.sampling.parent_base_sampler"] = "lib/opentelemetry/trace/sampling/parent_base_sampler.lua", 63 | ["opentelemetry.trace.sampling.result"] = "lib/opentelemetry/trace/sampling/result.lua", 64 | ["opentelemetry.trace.sampling.trace_id_ratio_sampler"] = "lib/opentelemetry/trace/sampling/trace_id_ratio_sampler.lua", 65 | ["opentelemetry.trace.span_context"] = "lib/opentelemetry/trace/span_context.lua", 66 | ["opentelemetry.trace.span_kind"] = "lib/opentelemetry/trace/span_kind.lua", 67 | ["opentelemetry.trace.span_status"] = "lib/opentelemetry/trace/span_status.lua", 68 | ["opentelemetry.trace.tracer"] = "lib/opentelemetry/trace/tracer.lua", 69 | ["opentelemetry.trace.tracer_provider"] = "lib/opentelemetry/trace/tracer_provider.lua", 70 | ["opentelemetry.trace.tracestate"] = "lib/opentelemetry/trace/tracestate.lua", 71 | ["opentelemetry.baggage"] = "lib/opentelemetry/baggage.lua", 72 | ["opentelemetry.baggage.propagation.text_map.baggage_propagator"] = "lib/opentelemetry/baggage/propagation/text_map/baggage_propagator.lua", 73 | ["opentelemetry.util"] = "lib/opentelemetry/util.lua" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/opentelemetry/trace/exporter/otlp.lua: -------------------------------------------------------------------------------- 1 | local encoder = require("opentelemetry.trace.exporter.encoder") 2 | local pb = require("opentelemetry.trace.exporter.pb") 3 | local otel_global = require("opentelemetry.global") 4 | local util = require("opentelemetry.util") 5 | local BACKOFF_RETRY_LIMIT = 3 6 | local DEFAULT_TIMEOUT_MS = 10000 7 | local exporter_request_duration_metric = "otel.otlp_exporter.request_duration" 8 | local circuit = require("opentelemetry.trace.exporter.circuit") 9 | 10 | local _M = { 11 | } 12 | 13 | local mt = { 14 | __index = _M 15 | } 16 | 17 | function _M.new(http_client, timeout_ms, circuit_reset_timeout_ms, circuit_open_threshold) 18 | local self = { 19 | client = http_client, 20 | timeout_ms = timeout_ms or DEFAULT_TIMEOUT_MS, 21 | circuit = circuit.new({ 22 | reset_timeout_ms = circuit_reset_timeout_ms, 23 | failure_threshold = circuit_open_threshold 24 | }) 25 | } 26 | return setmetatable(self, mt) 27 | end 28 | 29 | -------------------------------------------------------------------------------- 30 | -- Repeatedly make calls to collector until success, failure threshold or 31 | -- timeout 32 | -- 33 | -- @param exporter The exporter to use for the collector call 34 | -- @param pb_encoded_body protobuf-encoded body to send to the collector 35 | -- @return true if call succeeded; false if call failed 36 | -- @return nil if call succeeded; error message string if call failed 37 | -------------------------------------------------------------------------------- 38 | local function call_collector(exporter, pb_encoded_body) 39 | local start_time_ms = util.gettimeofday_ms() 40 | local failures = 0 41 | local res 42 | local res_error 43 | 44 | while failures < BACKOFF_RETRY_LIMIT do 45 | local current_time = util.gettimeofday_ms() 46 | if current_time - start_time_ms > exporter.timeout_ms then 47 | local err_message = "Collector retries timed out (timeout " .. exporter.timeout_ms .. ")" 48 | ngx.log(ngx.WARN, err_message) 49 | return false, err_message 50 | end 51 | 52 | if not exporter.circuit:should_make_request() then 53 | ngx.log(ngx.INFO, "Circuit breaker is open") 54 | return false, "Circuit breaker is open" 55 | end 56 | 57 | -- Make request 58 | res, res_error = exporter.client:do_request(pb_encoded_body) 59 | local after_time = util.gettimeofday_ms() 60 | otel_global.metrics_reporter:record_value( 61 | exporter_request_duration_metric, after_time - current_time) 62 | 63 | if not res then 64 | exporter.circuit:record_failure() 65 | failures = failures + 1 66 | ngx.sleep(util.random_float(2 ^ failures)) 67 | ngx.log(ngx.INFO, "Retrying call to collector (retry #" .. failures .. ")") 68 | else 69 | exporter.circuit:record_success() 70 | return true, nil 71 | end 72 | end 73 | 74 | return false, res_error or "unknown" 75 | end 76 | 77 | function _M.encode_spans(self, spans) 78 | assert(spans[1]) 79 | 80 | local body = { 81 | resource_spans = { 82 | { 83 | resource = { 84 | attributes = spans[1].tracer.provider.resource.attrs, 85 | dropped_attributes_count = 0, 86 | }, 87 | instrumentation_library_spans = { 88 | { 89 | instrumentation_library = { 90 | name = spans[1].tracer.il.name, 91 | version = spans[1].tracer.il.version, 92 | }, 93 | spans = {} 94 | }, 95 | }, 96 | } 97 | } 98 | } 99 | local tracers = {} 100 | local providers = {} 101 | tracers[spans[1].tracer] = 1 102 | providers[spans[1].tracer.provider] = 1 103 | for _, span in ipairs(spans) do 104 | local rs_idx = providers[span.tracer.provider] 105 | local ils_idx = tracers[span.tracer] 106 | if not rs_idx then 107 | rs_idx = #body.resource_spans + 1 108 | ils_idx = 1 109 | providers[span.tracer.provider] = rs_idx 110 | tracers[span.tracer] = ils_idx 111 | table.insert( 112 | body.resource_spans, 113 | { 114 | resource = { 115 | attributes = span.tracer.provider.resource.attrs, 116 | dropped_attributes_count = 0, 117 | }, 118 | instrumentation_library_spans = { 119 | { 120 | instrumentation_library = { 121 | name = span.tracer.il.name, 122 | version = span.tracer.il.version, 123 | }, 124 | spans = {} 125 | }, 126 | }, 127 | }) 128 | elseif not ils_idx then 129 | ils_idx = #body.resource_spans[rs_idx].instrumentation_library_spans + 1 130 | tracers[span.tracer] = ils_idx 131 | table.insert( 132 | body.resource_spans[rs_idx].instrumentation_library_spans, 133 | { 134 | instrumentation_library = { 135 | name = span.tracer.il.name, 136 | version = span.tracer.il.version, 137 | }, 138 | spans = {} 139 | }) 140 | end 141 | table.insert( 142 | body.resource_spans[rs_idx].instrumentation_library_spans[ils_idx].spans, 143 | encoder.for_otlp(span)) 144 | end 145 | return body 146 | end 147 | 148 | function _M.export_spans(self, spans) 149 | return call_collector(self, pb.encode(self:encode_spans(spans))) 150 | end 151 | 152 | function _M.shutdown(self) 153 | 154 | end 155 | 156 | return _M 157 | --------------------------------------------------------------------------------