├── .gitignore ├── .luacheckrc ├── .luacov ├── .travis.yml ├── LICENSE ├── NEWS ├── README.md ├── opentracing-scm-0.rockspec ├── opentracing ├── init.lua ├── span.lua ├── span_context.lua └── tracer.lua └── spec ├── span_context_spec.lua ├── span_spec.lua └── tracer_spec.lua /.gitignore: -------------------------------------------------------------------------------- 1 | /luacov.report.out 2 | /luacov.stats.out 3 | /*.rock 4 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | std = "min" 2 | files["spec"] = { 3 | std = "+busted"; 4 | } 5 | -------------------------------------------------------------------------------- /.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | deletestats = true; 3 | include = { 4 | "/opentracing/[^/]+$"; 5 | }; 6 | exclude = { 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | sudo: false 4 | 5 | env: 6 | matrix: 7 | - LUA="lua 5.1" 8 | - LUA="lua 5.2" 9 | - LUA="lua 5.3" 10 | - LUA="lua 5.3" COMPAT53=no 11 | - LUA="luajit @" 12 | - LUA="luajit 2.0" 13 | - LUA="luajit 2.1" 14 | 15 | branches: 16 | only: 17 | - master 18 | 19 | before_install: 20 | - pip install hererocks 21 | - hererocks ~/hererocks -r^ --$LUA --cflags=$LUA_CFLAGS 22 | - export PATH=$PATH:~/hererocks/bin 23 | - eval $(luarocks path --bin) 24 | - luarocks install luacheck 25 | - luarocks install luacov-coveralls 26 | - luarocks install busted 27 | 28 | install: 29 | - luarocks install --only-deps opentracing-scm-0.rockspec 30 | 31 | script: 32 | - luacheck . 33 | - busted -c 34 | 35 | after_success: 36 | - luacov-coveralls -v 37 | 38 | notifications: 39 | email: 40 | on_success: change 41 | on_failure: always 42 | 43 | cache: 44 | directories: 45 | - $HOME/.cache/hererocks 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018 Kong Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | 0.0.2 - 2018-08-09 2 | 3 | - Allow unsetting a tag with :set_tag(k, nil) 4 | - Add span:tracer() to get a span's tracer object 5 | - Add span:context() to get a span's span_context 6 | - Add span:set_operation_name() 7 | - Add span:log_kv() to log key/value pairs in a single call 8 | - Rename baggage methods to have _item suffix 9 | 10 | 11 | 0.0.1 - 2018-05-18 12 | 13 | - Initial release 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Opentracing for Lua 2 | 3 | This library is a Lua platform API for OpenTracing 4 | 5 | ## Required Reading 6 | 7 | To fully understand this platform API, it's helpful to be familiar with the [OpenTracing project](http://opentracing.io) and 8 | [terminology](http://opentracing.io/documentation/pages/spec.html) more specifically. 9 | 10 | 11 | ## Quick Start 12 | 13 | Install the package using `luarocks` 14 | 15 | ```bash 16 | luarocks install opentracing 17 | ``` 18 | 19 | 20 | ## Conventions 21 | 22 | - All timestamps are in seconds 23 | 24 | 25 | ## License 26 | 27 | Apache License 2.0 28 | -------------------------------------------------------------------------------- /opentracing-scm-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "opentracing" 2 | version = "scm-0" 3 | 4 | source = { 5 | url = "git+https://github.com/kong/opentracing-lua.git"; 6 | } 7 | 8 | description = { 9 | summary = "Lua platform API for OpenTracing"; 10 | homepage = "https://github.com/kong/opentracing-lua"; 11 | license = "Apache 2.0"; 12 | } 13 | 14 | dependencies = { 15 | "lua >= 5.1"; 16 | "luatz"; 17 | "luaossl"; 18 | } 19 | 20 | build = { 21 | type = "builtin"; 22 | modules = { 23 | ["opentracing"] = "opentracing/init.lua"; 24 | ["opentracing.span"] = "opentracing/span.lua"; 25 | ["opentracing.span_context"] = "opentracing/span_context.lua"; 26 | ["opentracing.tracer"] = "opentracing/tracer.lua"; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /opentracing/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | _VERSION = nil; 3 | } 4 | -------------------------------------------------------------------------------- /opentracing/span.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | The internal data structure is modeled off the ZipKin Span JSON Structure 3 | This makes it cheaper to convert to JSON for submission to the ZipKin HTTP api; 4 | which Jaegar also implements. 5 | You can find it documented in this OpenAPI spec: 6 | https://github.com/openzipkin/zipkin-api/blob/7e33e977/zipkin2-api.yaml#L280 7 | ]] 8 | 9 | local span_methods = {} 10 | local span_mt = { 11 | __name = "opentracing.span"; 12 | __index = span_methods; 13 | } 14 | 15 | local function is(object) 16 | return getmetatable(object) == span_mt 17 | end 18 | 19 | local function new(tracer, context, name, start_timestamp) 20 | assert(tracer, "missing tracer") 21 | assert(context, "missing context") 22 | assert(type(name) == "string", "name should be a string") 23 | assert(type(start_timestamp) == "number", "invalid starting timestamp") 24 | return setmetatable({ 25 | tracer_ = tracer; 26 | context_ = context; 27 | name = name; 28 | timestamp = start_timestamp; 29 | duration = nil; 30 | -- Avoid allocations until needed 31 | baggage = nil; 32 | tags = nil; 33 | logs = nil; 34 | n_logs = 0; 35 | }, span_mt) 36 | end 37 | 38 | function span_methods:context() 39 | return self.context_ 40 | end 41 | 42 | function span_methods:tracer() 43 | return self.tracer_ 44 | end 45 | 46 | function span_methods:set_operation_name(name) 47 | assert(type(name) == "string", "name should be a string") 48 | self.name = name 49 | end 50 | 51 | function span_methods:start_child_span(name, start_timestamp) 52 | return self.tracer_:start_span(name, { 53 | start_timestamp = start_timestamp; 54 | child_of = self; 55 | }) 56 | end 57 | 58 | function span_methods:finish(finish_timestamp) 59 | assert(self.duration == nil, "span already finished") 60 | if finish_timestamp == nil then 61 | self.duration = self.tracer_:time() - self.timestamp 62 | else 63 | assert(type(finish_timestamp) == "number") 64 | local duration = finish_timestamp - self.timestamp 65 | assert(duration >= 0, "invalid finish timestamp") 66 | self.duration = duration 67 | end 68 | if self.context_.should_sample then 69 | self.tracer_:report(self) 70 | end 71 | return true 72 | end 73 | 74 | function span_methods:set_tag(key, value) 75 | assert(type(key) == "string", "invalid tag key") 76 | if value ~= nil then -- Validate value 77 | local vt = type(value) 78 | assert(vt == "string" or vt == "number" or vt == "boolean", 79 | "invalid tag value (expected string, number, boolean or nil)") 80 | end 81 | local tags = self.tags 82 | if tags then 83 | tags[key] = value 84 | elseif value ~= nil then 85 | tags = { 86 | [key] = value 87 | } 88 | self.tags = tags 89 | end 90 | return true 91 | end 92 | 93 | function span_methods:get_tag(key) 94 | assert(type(key) == "string", "invalid tag key") 95 | local tags = self.tags 96 | if tags then 97 | return tags[key] 98 | else 99 | return nil 100 | end 101 | end 102 | 103 | function span_methods:each_tag() 104 | local tags = self.tags 105 | if tags == nil then return function() end end 106 | return next, tags 107 | end 108 | 109 | function span_methods:log(key, value, timestamp) 110 | assert(type(key) == "string", "invalid log key") 111 | -- `value` is allowed to be anything. 112 | if timestamp == nil then 113 | timestamp = self.tracer_:time() 114 | else 115 | assert(type(timestamp) == "number", "invalid timestamp for log") 116 | end 117 | 118 | local log = { 119 | key = key; 120 | value = value; 121 | timestamp = timestamp; 122 | } 123 | 124 | local logs = self.logs 125 | if logs then 126 | local i = self.n_logs + 1 127 | logs[i] = log 128 | self.n_logs = i 129 | else 130 | logs = { log } 131 | self.logs = logs 132 | self.n_logs = 1 133 | end 134 | return true 135 | end 136 | 137 | function span_methods:log_kv(key_values, timestamp) 138 | if timestamp == nil then 139 | timestamp = self.tracer_:time() 140 | else 141 | assert(type(timestamp) == "number", "invalid timestamp for log") 142 | end 143 | 144 | local logs = self.logs 145 | local n_logs 146 | if logs then 147 | n_logs = 0 148 | else 149 | n_logs = self.n_logs 150 | logs = { } 151 | self.logs = logs 152 | end 153 | 154 | for key, value in pairs(key_values) do 155 | n_logs = n_logs + 1 156 | logs[n_logs] = { 157 | key = key; 158 | value = value; 159 | timestamp = timestamp; 160 | } 161 | end 162 | 163 | self.n_logs = n_logs 164 | return true 165 | end 166 | 167 | function span_methods:each_log() 168 | local i = 0 169 | return function(logs) 170 | if i >= self.n_logs then 171 | return 172 | end 173 | i = i + 1 174 | local log = logs[i] 175 | return log.key, log.value, log.timestamp 176 | end, self.logs 177 | end 178 | 179 | function span_methods:set_baggage_item(key, value) 180 | -- Create new context so that baggage is immutably passed around 181 | local newcontext = self.context_:clone_with_baggage_item(key, value) 182 | self.context_ = newcontext 183 | return true 184 | end 185 | 186 | function span_methods:get_baggage_item(key) 187 | return self.context_:get_baggage_item(key) 188 | end 189 | 190 | function span_methods:each_baggage_item() 191 | return self.context_:each_baggage_item() 192 | end 193 | 194 | return { 195 | new = new; 196 | is = is; 197 | } 198 | -------------------------------------------------------------------------------- /opentracing/span_context.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Span contexts should be immutable 3 | ]] 4 | 5 | local rand_bytes = require "openssl.rand".bytes 6 | 7 | -- For zipkin compat, use 128 bit trace ids 8 | local function generate_trace_id() 9 | return rand_bytes(16) 10 | end 11 | 12 | -- For zipkin compat, use 64 bit span ids 13 | local function generate_span_id() 14 | return rand_bytes(8) 15 | end 16 | 17 | local span_context_methods = {} 18 | local span_context_mt = { 19 | __name = "opentracing.span_context"; 20 | __index = span_context_methods; 21 | } 22 | 23 | local function is(object) 24 | return getmetatable(object) == span_context_mt 25 | end 26 | 27 | local baggage_mt = { 28 | __name = "opentracing.span_context.baggage"; 29 | __newindex = function() 30 | error("attempt to set immutable baggage") 31 | end; 32 | } 33 | 34 | -- Public constructor 35 | local function new(trace_id, span_id, parent_id, should_sample, baggage) 36 | if trace_id == nil then 37 | trace_id = generate_trace_id() 38 | else 39 | assert(type(trace_id) == "string", "invalid trace id") 40 | end 41 | if span_id == nil then 42 | span_id = generate_span_id() 43 | else 44 | assert(type(span_id) == "string", "invalid span id") 45 | end 46 | if parent_id ~= nil then 47 | assert(type(parent_id) == "string", "invalid parent id") 48 | end 49 | if baggage then 50 | local new_baggage = {} 51 | for key, value in pairs(baggage) do 52 | assert(type(key) == "string", "invalid baggage key") 53 | assert(type(value) == "string", "invalid baggage value") 54 | new_baggage[key] = value 55 | end 56 | baggage = setmetatable(new_baggage, baggage_mt) 57 | end 58 | return setmetatable({ 59 | trace_id = trace_id; 60 | span_id = span_id; 61 | parent_id = parent_id; 62 | should_sample = should_sample; 63 | baggage = baggage; 64 | }, span_context_mt) 65 | end 66 | 67 | function span_context_methods:child() 68 | return setmetatable({ 69 | trace_id = self.trace_id; 70 | span_id = generate_span_id(); 71 | parent_id = self.span_id; 72 | -- If parent was sampled, sample the child 73 | should_sample = self.should_sample; 74 | baggage = self.baggage; 75 | }, span_context_mt) 76 | end 77 | 78 | -- New from existing but with an extra baggage item 79 | function span_context_methods:clone_with_baggage_item(key, value) 80 | assert(type(key) == "string", "invalid baggage key") 81 | assert(type(value) == "string", "invalid baggage value") 82 | local new_baggage = {} 83 | if self.baggage then 84 | for k, v in pairs(self.baggage) do 85 | new_baggage[k] = v 86 | end 87 | end 88 | new_baggage[key] = value 89 | return setmetatable({ 90 | trace_id = self.trace_id; 91 | span_id = self.span_id; 92 | parent_id = self.parent_id; 93 | should_sample = self.should_sample; 94 | baggage = setmetatable(new_baggage, baggage_mt); 95 | }, span_context_mt) 96 | end 97 | 98 | function span_context_methods:get_baggage_item(key) 99 | assert(type(key) == "string", "invalid baggage key") 100 | local baggage = self.baggage 101 | if baggage == nil then 102 | return nil 103 | else 104 | return baggage[key] 105 | end 106 | end 107 | 108 | function span_context_methods:each_baggage_item() 109 | local baggage = self.baggage 110 | if baggage == nil then return function() end end 111 | return next, baggage 112 | end 113 | 114 | return { 115 | new = new; 116 | is = is; 117 | } 118 | -------------------------------------------------------------------------------- /opentracing/tracer.lua: -------------------------------------------------------------------------------- 1 | local gettime = require "luatz.gettime".gettime 2 | local opentracing_span = require "opentracing.span" 3 | local opentracing_span_context = require "opentracing.span_context" 4 | 5 | local tracer_methods = {} 6 | local tracer_mt = { 7 | __name = "opentracing.tracer"; 8 | __index = tracer_methods; 9 | } 10 | 11 | local function is(object) 12 | return getmetatable(object) == tracer_mt 13 | end 14 | 15 | local no_op_reporter = { 16 | report = function() end; 17 | } 18 | local no_op_sampler = { 19 | sample = function() return false end; 20 | } 21 | 22 | -- Make injectors and extractors weakly keyed so that unreferenced formats get dropped 23 | local injectors_metatable = { 24 | __name = "opentracing.tracer.injectors"; 25 | __mode = "k"; 26 | } 27 | local extractors_metatable = { 28 | __name = "opentracing.tracer.extractors"; 29 | __mode = "k"; 30 | } 31 | 32 | local function new(reporter, sampler) 33 | if reporter == nil then 34 | reporter = no_op_reporter 35 | end 36 | if sampler == nil then 37 | sampler = no_op_sampler 38 | end 39 | return setmetatable({ 40 | injectors = setmetatable({}, injectors_metatable); 41 | extractors = setmetatable({}, extractors_metatable); 42 | reporter = reporter; 43 | sampler = sampler; 44 | }, tracer_mt) 45 | end 46 | 47 | function tracer_methods:start_span(name, options) 48 | local context, child_of, references, tags, extra_tags, start_timestamp 49 | if options ~= nil then 50 | child_of = options.child_of 51 | references = options.references 52 | if child_of ~= nil then 53 | assert(references == nil, "cannot specify both references and child_of") 54 | if opentracing_span.is(child_of) then 55 | child_of = child_of:context() 56 | else 57 | assert(opentracing_span_context.is(child_of), "child_of should be a span or span context") 58 | end 59 | end 60 | if references ~= nil then 61 | assert(type(references) == "table", "references should be a table") 62 | error("references NYI") 63 | end 64 | tags = options.tags 65 | if tags ~= nil then 66 | assert(type(tags) == "table", "tags should be a table") 67 | end 68 | start_timestamp = options.start_timestamp 69 | -- Allow opentracing_span.new to validate 70 | end 71 | if start_timestamp == nil then 72 | start_timestamp = self:time() 73 | end 74 | if child_of then 75 | context = child_of:child() 76 | else 77 | local should_sample 78 | should_sample, extra_tags = self.sampler:sample(name) 79 | context = opentracing_span_context.new(nil, nil, nil, should_sample) 80 | end 81 | local span = opentracing_span.new(self, context, name, start_timestamp) 82 | if extra_tags then 83 | for k, v in pairs(extra_tags) do 84 | span:set_tag(k, v) 85 | end 86 | end 87 | if tags then 88 | for k, v in pairs(tags) do 89 | span:set_tag(k, v) 90 | end 91 | end 92 | return span 93 | end 94 | 95 | -- Spans belonging to this tracer will get timestamps via this method 96 | -- Can be overridden for e.g. testing 97 | function tracer_methods:time() -- luacheck: ignore 212 98 | return gettime() 99 | end 100 | 101 | function tracer_methods:report(span) 102 | return self.reporter:report(span) 103 | end 104 | 105 | function tracer_methods:register_injector(format, injector) 106 | assert(format, "invalid format") 107 | assert(injector, "invalid injector") 108 | self.injectors[format] = injector 109 | return true 110 | end 111 | 112 | function tracer_methods:register_extractor(format, extractor) 113 | assert(format, "invalid format") 114 | assert(extractor, "invalid extractor") 115 | self.extractors[format] = extractor 116 | return true 117 | end 118 | 119 | function tracer_methods:inject(context, format, carrier) 120 | if opentracing_span.is(context) then 121 | context = context:context() 122 | else 123 | assert(opentracing_span_context.is(context), "context should be a span or span context") 124 | end 125 | local injector = self.injectors[format] 126 | if injector == nil then 127 | error("Unknown format: " .. format) 128 | end 129 | return injector(context, carrier) 130 | end 131 | 132 | function tracer_methods:extract(format, carrier) 133 | local extractor = self.extractors[format] 134 | if extractor == nil then 135 | error("Unknown format: " .. format) 136 | end 137 | return extractor(carrier) 138 | end 139 | 140 | return { 141 | new = new; 142 | is = is; 143 | } 144 | -------------------------------------------------------------------------------- /spec/span_context_spec.lua: -------------------------------------------------------------------------------- 1 | describe("opentracing.span_context", function() 2 | local opentracing_span_context = require "opentracing.span_context" 3 | local new_context = opentracing_span_context.new 4 | it("has working .is function", function() 5 | assert.falsy(opentracing_span_context.is(nil)) 6 | assert.falsy(opentracing_span_context.is({})) 7 | local context = new_context() 8 | assert.truthy(opentracing_span_context.is(context)) 9 | end) 10 | it("doesn't allow constructing with invalid trace id", function() 11 | assert.has.errors(function() 12 | new_context({}) 13 | end) 14 | end) 15 | it("doesn't allow constructing with invalid span id", function() 16 | assert.has.errors(function() 17 | new_context(nil, {}) 18 | end) 19 | end) 20 | it("doesn't allow constructing with invalid parent id", function() 21 | assert.has.errors(function() 22 | new_context(nil, nil, {}) 23 | end) 24 | end) 25 | it("allows constructing with baggage items", function() 26 | local baggage_arg = { 27 | foo = "bar"; 28 | somekey = "some value"; 29 | } 30 | local context = new_context(nil, nil, nil, nil, baggage_arg) 31 | assert.same("bar", context:get_baggage_item("foo")) 32 | assert.same("some value", context:get_baggage_item("somekey")) 33 | baggage_arg.modified = "other" 34 | assert.same(nil, context:get_baggage_item("modified")) 35 | end) 36 | end) 37 | -------------------------------------------------------------------------------- /spec/span_spec.lua: -------------------------------------------------------------------------------- 1 | describe("opentracing.span", function() 2 | local tracer = require "opentracing.tracer".new() 3 | local context = require "opentracing.span_context".new() 4 | local opentracing_span = require "opentracing.span" 5 | local new_span = opentracing_span.new 6 | it("has working .is function", function() 7 | assert.falsy(opentracing_span.is(nil)) 8 | assert.falsy(opentracing_span.is({})) 9 | local span = new_span(tracer, context, "foo", 0) 10 | assert.truthy(opentracing_span.is(span)) 11 | assert.falsy(opentracing_span.is(tracer)) 12 | assert.falsy(opentracing_span.is(context)) 13 | end) 14 | it("doesn't allow constructing without a tracer", function() 15 | assert.has.errors(function() 16 | new_span(nil, context, "foo") 17 | end) 18 | end) 19 | it("doesn't allow constructing without a context", function() 20 | assert.has.errors(function() 21 | new_span(tracer, nil, "foo") 22 | end) 23 | end) 24 | it("doesn't allow constructing without a name", function() 25 | assert.has.errors(function() 26 | new_span(tracer, context, nil) 27 | end) 28 | end) 29 | it("asks for time from tracer when not passed", function() 30 | local mock_tracer = mock({ 31 | time = function() return 42 end; 32 | }) 33 | local span = new_span(mock_tracer, context, "foo", 0) 34 | span:log("key", "value") 35 | assert.spy(mock_tracer.time).was.called(1) 36 | span:log_kv{key = "value"} 37 | assert.spy(mock_tracer.time).was.called(2) 38 | span:finish() 39 | assert.spy(mock_tracer.time).was.called(3) 40 | end) 41 | it("doesn't allow constructing with invalid timestamp", function() 42 | assert.has.errors(function() 43 | new_span(tracer, context, "foo", {}) 44 | end) 45 | end) 46 | it("can retreive context with :context()", function() 47 | local span = new_span(tracer, context, "foo", 0) 48 | assert.same(context, span:context()) 49 | end) 50 | it("can retreive tracer with :tracer()", function() 51 | local span = new_span(tracer, context, "foo", 0) 52 | assert.same(tracer, span:tracer()) 53 | end) 54 | it("can change name with :set_operation_name", function() 55 | local span = new_span(tracer, context, "foo", 0) 56 | span:set_operation_name("bar") 57 | assert.same("bar", span.name) 58 | end) 59 | it("can construct with :start_child_span", function() 60 | local span1 = new_span(tracer, context, "foo", 0) 61 | local span2 = span1:start_child_span("bar", 1) 62 | assert.same("foo", span1.name) 63 | assert.same(0, span1.timestamp) 64 | assert.same("bar", span2.name) 65 | assert.same(1, span2.timestamp) 66 | end) 67 | it("doesn't allow :finish with invalid timestamp", function() 68 | local span = new_span(tracer, context, "foo", 0) 69 | assert.has.errors(function() 70 | span:finish({}) 71 | end) 72 | end) 73 | it("doesn't allow :finish-ing twice", function() 74 | local span = new_span(tracer, context, "foo", 0) 75 | span:finish(10) 76 | assert.has.errors(function() 77 | span:finish(11) 78 | end) 79 | end) 80 | it("can iterate over empty set of tags", function() 81 | local span = new_span(tracer, context, "foo", 0) 82 | for _ in span:each_tag() do 83 | error("unreachable") 84 | end 85 | end) 86 | it("can :get_tag", function() 87 | local span = new_span(tracer, context, "foo", 0) 88 | assert.same(nil, span:get_tag("http.method")) 89 | span:set_tag("http.method", "GET") 90 | assert.same("GET", span:get_tag("http.method")) 91 | end) 92 | it("can :set_tag(k, nil) to clear a tags", function() 93 | local span = new_span(tracer, context, "foo", 0) 94 | assert.same(nil, span:get_tag("http.method")) 95 | span:set_tag("http.method", "GET") 96 | assert.same("GET", span:get_tag("http.method")) 97 | span:set_tag("http.method", nil) 98 | assert.same(nil, span:get_tag("http.method")) 99 | end) 100 | it("can iterate over tags", function() 101 | local span = new_span(tracer, context, "foo", 0) 102 | local tags = { 103 | ["http.method"] = "GET"; 104 | ["http.url"] = "https://example.com/"; 105 | } 106 | for k, v in pairs(tags) do 107 | span:set_tag(k, v) 108 | end 109 | local seen = {} 110 | for k, v in span:each_tag() do 111 | seen[k] = v 112 | end 113 | assert.same(tags, seen) 114 | end) 115 | it("can iterate over empty logs collection", function() 116 | local span = new_span(tracer, context, "foo", 0) 117 | for _ in span:each_log() do 118 | error("unreachable") 119 | end 120 | end) 121 | it("can iterate over logs", function() 122 | local span = new_span(tracer, context, "foo", 0) 123 | local logs = { 124 | ["thing1"] = 1000; -- valid value **and** valid timestamp 125 | ["thing2"] = 1001; 126 | } 127 | for k, v in pairs(logs) do 128 | local t = v*10 129 | span:log(k, v, t) 130 | end 131 | local seen = {} 132 | for k, v, t in span:each_log() do 133 | assert.same(v*10, t) 134 | seen[k] = v 135 | end 136 | assert.same(logs, seen) 137 | end) 138 | it("logs are created with :log_kv", function() 139 | local span = new_span(tracer, context, "foo", 0) 140 | local logs = { 141 | ["thing1"] = 1000; 142 | ["thing2"] = 1001; 143 | } 144 | span:log_kv(logs, 1234) 145 | local seen = {} 146 | for k, v, t in span:each_log() do 147 | assert.same(1234, t) 148 | seen[k] = v 149 | end 150 | assert.same(logs, seen) 151 | end) 152 | it("tracks baggage", function() 153 | local span = new_span(tracer, context, "name", 0) 154 | -- New span shouldn't have any baggage 155 | assert.same(nil, span:get_baggage_item("foo")) 156 | -- Check normal case 157 | span:set_baggage_item("foo", "bar") 158 | assert.same("bar", span:get_baggage_item("foo")) 159 | -- Make sure adding a new key doesn't remove old ones 160 | span:set_baggage_item("mykey", "myvalue") 161 | assert.same("bar", span:get_baggage_item("foo")) 162 | assert.same("myvalue", span:get_baggage_item("mykey")) 163 | -- Set same key again and make sure it has changed 164 | span:set_baggage_item("foo", "other") 165 | assert.same("other", span:get_baggage_item("foo")) 166 | end) 167 | it("can iterate over baggage", function() 168 | local span = new_span(tracer, context, "foo", 0) 169 | local baggage = { 170 | ["baggage1"] = "value1"; 171 | ["baggage2"] = "value2"; 172 | } 173 | for k, v in pairs(baggage) do 174 | span:set_baggage_item(k, v) 175 | end 176 | local seen = {} 177 | for k, v in span:each_baggage_item() do 178 | seen[k] = v 179 | end 180 | assert.same(baggage, seen) 181 | end) 182 | end) 183 | -------------------------------------------------------------------------------- /spec/tracer_spec.lua: -------------------------------------------------------------------------------- 1 | describe("opentracing.tracer", function() 2 | local opentracing_span_context = require "opentracing.span_context" 3 | local opentracing_tracer = require "opentracing.tracer" 4 | local new_tracer = opentracing_tracer.new 5 | it("has working .is function", function() 6 | assert.falsy(opentracing_tracer.is(nil)) 7 | assert.falsy(opentracing_tracer.is({})) 8 | local tracer = new_tracer() 9 | assert.truthy(opentracing_tracer.is(tracer)) 10 | end) 11 | it("doesn't allow constructing span without a name", function() 12 | local tracer = new_tracer() 13 | assert.has.errors(function() 14 | tracer:start_span(nil) 15 | end) 16 | end) 17 | it("calls sampler for root traces", function() 18 | local mock_sampler = mock { 19 | sample = function() return false end; 20 | } 21 | local tracer = new_tracer(nil, mock_sampler) 22 | tracer:start_span("foo") 23 | assert.spy(mock_sampler.sample).was.called_with(mock_sampler, "foo") 24 | end) 25 | it("takes returned sampler tags into account", function() 26 | local mock_sampler = mock { 27 | sample = function() 28 | return true, { 29 | ["sampler.type"] = "mock"; 30 | } 31 | end; 32 | } 33 | local tracer = new_tracer(nil, mock_sampler) 34 | local span = tracer:start_span("foo") 35 | assert.spy(mock_sampler.sample).was.called_with(mock_sampler, "foo") 36 | local tags = {} 37 | for k, v in span:each_tag() do 38 | tags[k] = v 39 | end 40 | assert.same({["sampler.type"] = "mock";}, tags) 41 | end) 42 | it("calls reporter at end of span", function() 43 | local mock_sampler = mock { 44 | sample = function() return true end; 45 | } 46 | local mock_reporter = mock { 47 | report = function() end; 48 | } 49 | local tracer = new_tracer(mock_reporter, mock_sampler) 50 | local span = tracer:start_span("foo") 51 | assert.spy(mock_sampler.sample).was.called_with(mock_sampler, "foo") 52 | span:finish() 53 | assert.spy(mock_reporter.report).was.called_with(mock_reporter, span) 54 | end) 55 | it("allows passing in tags", function() 56 | local tracer = new_tracer() 57 | local tags = { 58 | ["http.method"] = "GET"; 59 | ["http.url"] = "https://example.com/"; 60 | } 61 | local span = tracer:start_span("foo", { 62 | tags = tags 63 | }) 64 | local seen = {} 65 | for k, v in span:each_tag() do 66 | seen[k] = v 67 | end 68 | assert.same(tags, seen) 69 | end) 70 | it("allows passing span as a child_of", function() 71 | local tracer = new_tracer() 72 | local span1 = tracer:start_span("foo") 73 | tracer:start_span("bar", { 74 | child_of = span1 75 | }) 76 | end) 77 | it("allows passing span context as a child_of", function() 78 | local tracer = new_tracer() 79 | local span1 = tracer:start_span("foo") 80 | tracer:start_span("bar", { 81 | child_of = span1:context() 82 | }) 83 | end) 84 | it("doesn't allow invalid child_of", function() 85 | local tracer = new_tracer() 86 | assert.has.errors(function() 87 | tracer:start_span("foo", { 88 | child_of = {} 89 | }) 90 | end) 91 | end) 92 | it("doesn't allow invalid references", function() 93 | local tracer = new_tracer() 94 | assert.has.errors(function() 95 | tracer:start_span("foo", { 96 | references = true 97 | }) 98 | end) 99 | end) 100 | it("works with custom extractor", function() 101 | local tracer = new_tracer() 102 | local mock_extractor = spy.new(function() 103 | local context = opentracing_span_context.new() 104 | return context 105 | end) 106 | tracer:register_extractor("my_type", mock_extractor) 107 | local carrier = {} 108 | tracer:extract("my_type", carrier) 109 | assert.spy(mock_extractor).was.called_with(carrier) 110 | end) 111 | it("checks for known extractor", function() 112 | local tracer = new_tracer() 113 | assert.has.errors(function() 114 | tracer:extract("my_unknown_type", {}) 115 | end) 116 | end) 117 | it("works with custom injector", function() 118 | local tracer = new_tracer() 119 | local mock_injector = stub() 120 | tracer:register_injector("my_type", mock_injector) 121 | 122 | local span = tracer:start_span("foo") 123 | local context = span:context() 124 | local carrier = {} 125 | tracer:inject(context, "my_type", carrier) 126 | assert.stub(mock_injector).was.called_with(context, carrier) 127 | end) 128 | it(":inject takes span", function() 129 | local tracer = new_tracer() 130 | local mock_injector = stub() 131 | tracer:register_injector("my_type", mock_injector) 132 | local span = tracer:start_span("foo") 133 | local context = span:context() 134 | local carrier = {} 135 | tracer:inject(span, "my_type", carrier) 136 | assert.stub(mock_injector).was.called_with(context, carrier) 137 | end) 138 | it("checks for known injector", function() 139 | local tracer = new_tracer() 140 | local span = tracer:start_span("foo") 141 | local context = span:context() 142 | assert.has.errors(function() 143 | tracer:inject(context, "my_unknown_type", {}) 144 | end) 145 | end) 146 | end) 147 | --------------------------------------------------------------------------------