├── .bulldozer.yml ├── .changelog.yml ├── .circleci └── config.yml ├── .excavator.yml ├── .github └── dependabot.yml ├── .gitignore ├── .palantir └── autorelease.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── http-zipkin ├── Cargo.toml └── src │ └── lib.rs ├── zipkin-macros ├── Cargo.toml └── src │ └── lib.rs ├── zipkin-types ├── Cargo.toml └── src │ ├── annotation.rs │ ├── endpoint.rs │ ├── lib.rs │ ├── span.rs │ ├── span_id.rs │ └── trace_id.rs └── zipkin ├── Cargo.toml └── src ├── current.rs ├── lib.rs ├── open_span.rs ├── report.rs ├── sample.rs ├── sampling_flags.rs ├── test ├── macros.rs └── mod.rs ├── trace_context.rs └── tracer.rs /.bulldozer.yml: -------------------------------------------------------------------------------- 1 | # Excavator auto-updates this file. Please contribute improvements to the central template. 2 | 3 | version: 1 4 | merge: 5 | trigger: 6 | labels: ["merge when ready"] 7 | ignore: 8 | labels: ["do not merge"] 9 | method: squash 10 | options: 11 | squash: 12 | body: pull_request_body 13 | message_delimiter: ==COMMIT_MSG== 14 | delete_after_merge: true 15 | update: 16 | trigger: 17 | labels: ["update me"] 18 | -------------------------------------------------------------------------------- /.changelog.yml: -------------------------------------------------------------------------------- 1 | # Excavator auto-updates this file. Please contribute improvements to the central template. 2 | 3 | # This file is intentionally empty. The file's existence enables changelog-app and is empty to use the default configuration. 4 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | restore_registry: &RESTORE_REGISTRY 2 | restore_cache: 3 | key: registry 4 | save_registry: &SAVE_REGISTRY 5 | save_cache: 6 | key: registry-{{ .BuildNum }} 7 | paths: 8 | - /usr/local/cargo/registry/index 9 | deps_key: &DEPS_KEY 10 | key: deps-{{ checksum "~/rust-version" }}-{{ checksum "Cargo.lock" }} 11 | restore_deps: &RESTORE_DEPS 12 | restore_cache: 13 | <<: *DEPS_KEY 14 | save_deps: &SAVE_DEPS 15 | save_cache: 16 | <<: *DEPS_KEY 17 | paths: 18 | - target 19 | - /usr/local/cargo/registry/cache 20 | 21 | version: 2 22 | jobs: 23 | build: 24 | docker: 25 | - image: rust:1.84.0 26 | environment: 27 | RUSTFLAGS: -D warnings 28 | steps: 29 | - checkout 30 | - run: rustup component add rustfmt clippy 31 | - *RESTORE_REGISTRY 32 | - run: cargo generate-lockfile 33 | - *SAVE_REGISTRY 34 | - run: rustc --version > ~/rust-version 35 | - *RESTORE_DEPS 36 | - run: cargo fmt --all -- --check 37 | - run: cargo clippy --all --all-targets 38 | - run: cargo test --all 39 | - run: cargo test --all --all-features 40 | - *SAVE_DEPS 41 | circle-all: 42 | docker: 43 | - image: busybox:1.34.1 44 | resource_class: small 45 | steps: 46 | - run: 47 | command: echo "All required jobs finished successfully" 48 | workflows: 49 | version: 2 50 | build: 51 | jobs: 52 | - build: 53 | requires: [] 54 | filters: 55 | tags: 56 | only: /.*/ 57 | - circle-all: 58 | requires: 59 | - build 60 | filters: 61 | tags: 62 | only: /.*/ 63 | -------------------------------------------------------------------------------- /.excavator.yml: -------------------------------------------------------------------------------- 1 | # Excavator auto-updates this file. Please contribute improvements to the central template. 2 | 3 | auto-label: 4 | names: 5 | versions-props/upgrade-all: [ "merge when ready" ] 6 | circleci/manage-circleci: [ "merge when ready" ] 7 | tags: 8 | donotmerge: [ "do not merge" ] 9 | roomba: [ "merge when ready" ] 10 | automerge: [ "merge when ready" ] 11 | autorelease: [ "autorelease" ] 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "13:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | .vscode/ 5 | .idea/ 6 | *.iml 7 | -------------------------------------------------------------------------------- /.palantir/autorelease.yml: -------------------------------------------------------------------------------- 1 | version: 3 2 | options: 3 | repo_type: RUST 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace.package] 2 | version = "1.0.0" 3 | 4 | [workspace] 5 | members = [ 6 | "http-zipkin", 7 | "zipkin", 8 | "zipkin-macros", 9 | "zipkin-types", 10 | ] 11 | -------------------------------------------------------------------------------- /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 {yyyy} {name of copyright owner} 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 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-zipkin 2 | 3 | [![CircleCI](https://circleci.com/gh/palantir/rust-zipkin.svg?style=shield)](https://circleci.com/gh/palantir/rust-zipkin) 4 | 5 | A collection of crates to support the Zipkin distributed tracing system. 6 | 7 | ## zipkin-types 8 | 9 | [Documentation](https://docs.rs/zipkin-types) 10 | 11 | The zipkin-types crate defines Rust types corresponding to Zipkin's object 12 | schema. 13 | 14 | ## zipkin 15 | 16 | [Documentation](https://docs.rs/zipkin) 17 | 18 | The zipkin crate defines a `Tracer` object which handles the heavy lifting of 19 | creating and recording Zipkin spans. 20 | 21 | ## http-zipkin 22 | 23 | [Documentation](https://docs.rs/http-zipkin) 24 | 25 | The http-zipkin crate provides functions to serialize and deserialize zipkin 26 | `TraceContext` and `SamplingFlags` values into and out of http `HeaderMap` 27 | collections to propagate traces across HTTP requests. 28 | 29 | ## License 30 | 31 | This repository is made available under the [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0). 32 | -------------------------------------------------------------------------------- /http-zipkin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http-zipkin" 3 | version.workspace = true 4 | authors = ["Steven Fackler "] 5 | edition = "2018" 6 | license = "Apache-2.0" 7 | description = "HTTP header propagation for Zipkin trace information." 8 | repository = "https://github.com/palantir/rust-zipkin" 9 | readme = "../README.md" 10 | categories = ["web-programming"] 11 | keywords = ["zipkin", "tracing"] 12 | 13 | [dependencies] 14 | http = "1.0" 15 | zipkin = { version = "1.0.0", path = "../zipkin" } 16 | -------------------------------------------------------------------------------- /http-zipkin/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Palantir Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! HTTP header propagation for Zipkin trace information. 16 | #![doc(html_root_url = "https://docs.rs/http-zipkin/0.3")] 17 | #![warn(missing_docs)] 18 | 19 | use http::header::{HeaderMap, HeaderValue}; 20 | use std::fmt::Write; 21 | use std::str::FromStr; 22 | use zipkin::{SamplingFlags, TraceContext}; 23 | 24 | const X_B3_SAMPLED: &str = "X-B3-Sampled"; 25 | const X_B3_FLAGS: &str = "X-B3-Flags"; 26 | const X_B3_TRACEID: &str = "X-B3-TraceId"; 27 | const X_B3_PARENTSPANID: &str = "X-B3-ParentSpanId"; 28 | const X_B3_SPANID: &str = "X-B3-SpanId"; 29 | const B3: &str = "b3"; 30 | 31 | /// Serializes sampling flags into the `b3` HTTP header. 32 | /// 33 | /// This form is more compact than the old `X-B3-` set of headers, but some implementations may not support it. 34 | pub fn set_sampling_flags_single(flags: SamplingFlags, headers: &mut HeaderMap) { 35 | if flags.debug() { 36 | headers.insert(B3, HeaderValue::from_static("d")); 37 | } else if flags.sampled() == Some(true) { 38 | headers.insert(B3, HeaderValue::from_static("1")); 39 | } else if flags.sampled() == Some(false) { 40 | headers.insert(B3, HeaderValue::from_static("0")); 41 | } else { 42 | headers.remove(B3); 43 | } 44 | } 45 | 46 | /// Serializes sampling flags into a set of HTTP headers. 47 | pub fn set_sampling_flags(flags: SamplingFlags, headers: &mut HeaderMap) { 48 | if flags.debug() { 49 | headers.insert(X_B3_FLAGS, HeaderValue::from_static("1")); 50 | headers.remove(X_B3_SAMPLED); 51 | } else { 52 | headers.remove(X_B3_FLAGS); 53 | match flags.sampled() { 54 | Some(true) => { 55 | headers.insert(X_B3_SAMPLED, HeaderValue::from_static("1")); 56 | } 57 | Some(false) => { 58 | headers.insert(X_B3_SAMPLED, HeaderValue::from_static("0")); 59 | } 60 | None => { 61 | headers.remove(X_B3_SAMPLED); 62 | } 63 | } 64 | } 65 | } 66 | 67 | /// Deserializes sampling flags from a set of HTTP headers. 68 | pub fn get_sampling_flags(headers: &HeaderMap) -> SamplingFlags { 69 | match headers.get(B3) { 70 | Some(value) => get_sampling_flags_single(value), 71 | None => get_sampling_flags_multi(headers), 72 | } 73 | } 74 | 75 | fn get_sampling_flags_single(value: &HeaderValue) -> SamplingFlags { 76 | let mut builder = SamplingFlags::builder(); 77 | 78 | if value == "d" { 79 | builder.debug(true); 80 | } else if value == "1" { 81 | builder.sampled(true); 82 | } else if value == "0" { 83 | builder.sampled(false); 84 | } else if let Some(context) = get_trace_context_single(value) { 85 | return context.sampling_flags(); 86 | } 87 | 88 | builder.build() 89 | } 90 | 91 | fn get_sampling_flags_multi(headers: &HeaderMap) -> SamplingFlags { 92 | let mut builder = SamplingFlags::builder(); 93 | 94 | if let Some(flags) = headers.get(X_B3_FLAGS) { 95 | if flags == "1" { 96 | builder.debug(true); 97 | } 98 | } else if let Some(sampled) = headers.get(X_B3_SAMPLED) { 99 | if sampled == "1" { 100 | builder.sampled(true); 101 | } else if sampled == "0" { 102 | builder.sampled(false); 103 | } 104 | } 105 | 106 | builder.build() 107 | } 108 | 109 | /// Serializes a trace context into the `b3` header. 110 | /// 111 | /// This form is more compact than the old `X-B3-` set of headers, but some implementations may not support it. 112 | pub fn set_trace_context_single(context: TraceContext, headers: &mut HeaderMap) { 113 | let mut value = String::new(); 114 | write!(value, "{}-{}", context.trace_id(), context.span_id()).unwrap(); 115 | if context.debug() { 116 | value.push_str("-d"); 117 | } else if context.sampled() == Some(true) { 118 | value.push_str("-1"); 119 | } else if context.sampled() == Some(false) { 120 | value.push_str("-0"); 121 | } 122 | if let Some(parent_id) = context.parent_id() { 123 | write!(value, "-{}", parent_id).unwrap(); 124 | } 125 | headers.insert(B3, HeaderValue::from_str(&value).unwrap()); 126 | } 127 | 128 | /// Serializes a trace context into a set of HTTP headers. 129 | pub fn set_trace_context(context: TraceContext, headers: &mut HeaderMap) { 130 | set_sampling_flags(context.sampling_flags(), headers); 131 | 132 | headers.insert( 133 | X_B3_TRACEID, 134 | HeaderValue::from_str(&context.trace_id().to_string()).unwrap(), 135 | ); 136 | match context.parent_id() { 137 | Some(parent_id) => { 138 | headers.insert( 139 | X_B3_PARENTSPANID, 140 | HeaderValue::from_str(&parent_id.to_string()).unwrap(), 141 | ); 142 | } 143 | None => { 144 | headers.remove(X_B3_PARENTSPANID); 145 | } 146 | } 147 | headers.insert( 148 | X_B3_SPANID, 149 | HeaderValue::from_str(&context.span_id().to_string()).unwrap(), 150 | ); 151 | } 152 | 153 | /// Deserializes a trace context from a set of HTTP headers. 154 | pub fn get_trace_context(headers: &HeaderMap) -> Option { 155 | match headers.get(B3) { 156 | Some(value) => get_trace_context_single(value), 157 | None => get_trace_context_multi(headers), 158 | } 159 | } 160 | 161 | fn get_trace_context_single(value: &HeaderValue) -> Option { 162 | let mut parts = value.to_str().ok()?.split('-'); 163 | 164 | let trace_id = parts.next()?.parse().ok()?; 165 | let span_id = parts.next()?.parse().ok()?; 166 | 167 | let mut builder = TraceContext::builder(); 168 | builder.trace_id(trace_id).span_id(span_id); 169 | 170 | let maybe_sampling = match parts.next() { 171 | Some(next) => next, 172 | None => return Some(builder.build()), 173 | }; 174 | 175 | let parent_id = if maybe_sampling == "d" { 176 | builder.debug(true); 177 | parts.next() 178 | } else if maybe_sampling == "1" { 179 | builder.sampled(true); 180 | parts.next() 181 | } else if maybe_sampling == "0" { 182 | builder.sampled(false); 183 | parts.next() 184 | } else { 185 | Some(maybe_sampling) 186 | }; 187 | 188 | if let Some(parent_id) = parent_id { 189 | builder.parent_id(parent_id.parse().ok()?); 190 | } 191 | 192 | Some(builder.build()) 193 | } 194 | 195 | fn get_trace_context_multi(headers: &HeaderMap) -> Option { 196 | let trace_id = parse_header(headers, X_B3_TRACEID)?; 197 | let span_id = parse_header(headers, X_B3_SPANID)?; 198 | 199 | let mut builder = TraceContext::builder(); 200 | builder 201 | .trace_id(trace_id) 202 | .span_id(span_id) 203 | .sampling_flags(get_sampling_flags_multi(headers)); 204 | 205 | if let Some(parent_id) = parse_header(headers, X_B3_PARENTSPANID) { 206 | builder.parent_id(parent_id); 207 | } 208 | 209 | Some(builder.build()) 210 | } 211 | 212 | fn parse_header(headers: &HeaderMap, name: &str) -> Option 213 | where 214 | T: FromStr, 215 | { 216 | headers 217 | .get(name) 218 | .and_then(|v| v.to_str().ok()) 219 | .and_then(|s| s.parse().ok()) 220 | } 221 | 222 | #[cfg(test)] 223 | mod test { 224 | use super::*; 225 | 226 | #[test] 227 | fn flags_empty() { 228 | let mut headers = HeaderMap::new(); 229 | let flags = SamplingFlags::builder().build(); 230 | set_sampling_flags(flags, &mut headers); 231 | 232 | let expected_headers = HeaderMap::new(); 233 | assert_eq!(headers, expected_headers); 234 | 235 | assert_eq!(get_sampling_flags(&headers), flags); 236 | } 237 | 238 | #[test] 239 | fn flags_empty_single() { 240 | let mut headers = HeaderMap::new(); 241 | let flags = SamplingFlags::builder().build(); 242 | set_sampling_flags_single(flags, &mut headers); 243 | 244 | let expected_headers = HeaderMap::new(); 245 | assert_eq!(headers, expected_headers); 246 | 247 | assert_eq!(get_sampling_flags(&headers), flags); 248 | } 249 | 250 | #[test] 251 | fn flags_debug() { 252 | let mut headers = HeaderMap::new(); 253 | let flags = SamplingFlags::builder().debug(true).build(); 254 | set_sampling_flags(flags, &mut headers); 255 | 256 | let mut expected_headers = HeaderMap::new(); 257 | expected_headers.insert("X-B3-Flags", HeaderValue::from_static("1")); 258 | assert_eq!(headers, expected_headers); 259 | 260 | assert_eq!(get_sampling_flags(&headers), flags); 261 | } 262 | 263 | #[test] 264 | fn flags_debug_single() { 265 | let mut headers = HeaderMap::new(); 266 | let flags = SamplingFlags::builder().debug(true).build(); 267 | set_sampling_flags_single(flags, &mut headers); 268 | 269 | let mut expected_headers = HeaderMap::new(); 270 | expected_headers.insert("b3", HeaderValue::from_static("d")); 271 | assert_eq!(headers, expected_headers); 272 | 273 | assert_eq!(get_sampling_flags(&headers), flags); 274 | } 275 | 276 | #[test] 277 | fn flags_sampled() { 278 | let mut headers = HeaderMap::new(); 279 | let flags = SamplingFlags::builder().sampled(true).build(); 280 | set_sampling_flags(flags, &mut headers); 281 | 282 | let mut expected_headers = HeaderMap::new(); 283 | expected_headers.insert("X-B3-Sampled", HeaderValue::from_static("1")); 284 | assert_eq!(headers, expected_headers); 285 | 286 | assert_eq!(get_sampling_flags(&headers), flags); 287 | } 288 | 289 | #[test] 290 | fn flags_sampled_single() { 291 | let mut headers = HeaderMap::new(); 292 | let flags = SamplingFlags::builder().sampled(true).build(); 293 | set_sampling_flags_single(flags, &mut headers); 294 | 295 | let mut expected_headers = HeaderMap::new(); 296 | expected_headers.insert("b3", HeaderValue::from_static("1")); 297 | assert_eq!(headers, expected_headers); 298 | 299 | assert_eq!(get_sampling_flags(&headers), flags); 300 | } 301 | 302 | #[test] 303 | fn flags_unsampled() { 304 | let mut headers = HeaderMap::new(); 305 | let flags = SamplingFlags::builder().sampled(false).build(); 306 | set_sampling_flags(flags, &mut headers); 307 | 308 | let mut expected_headers = HeaderMap::new(); 309 | expected_headers.insert("X-B3-Sampled", HeaderValue::from_static("0")); 310 | assert_eq!(headers, expected_headers); 311 | 312 | assert_eq!(get_sampling_flags(&headers), flags); 313 | } 314 | 315 | #[test] 316 | fn flags_unsampled_single() { 317 | let mut headers = HeaderMap::new(); 318 | let flags = SamplingFlags::builder().sampled(false).build(); 319 | set_sampling_flags_single(flags, &mut headers); 320 | 321 | let mut expected_headers = HeaderMap::new(); 322 | expected_headers.insert("b3", HeaderValue::from_static("0")); 323 | assert_eq!(headers, expected_headers); 324 | 325 | assert_eq!(get_sampling_flags(&headers), flags); 326 | } 327 | 328 | #[test] 329 | fn trace_context() { 330 | let mut headers = HeaderMap::new(); 331 | let context = TraceContext::builder() 332 | .trace_id([0, 1, 2, 3, 4, 5, 6, 7].into()) 333 | .parent_id([1, 2, 3, 4, 5, 6, 7, 8].into()) 334 | .span_id([2, 3, 4, 5, 6, 7, 8, 9].into()) 335 | .sampled(true) 336 | .build(); 337 | set_trace_context(context, &mut headers); 338 | 339 | let mut expected_headers = HeaderMap::new(); 340 | expected_headers.insert("X-B3-TraceId", HeaderValue::from_static("0001020304050607")); 341 | expected_headers.insert("X-B3-SpanId", HeaderValue::from_static("0203040506070809")); 342 | expected_headers.insert( 343 | "X-B3-ParentSpanId", 344 | HeaderValue::from_static("0102030405060708"), 345 | ); 346 | expected_headers.insert("X-B3-Sampled", HeaderValue::from_static("1")); 347 | assert_eq!(headers, expected_headers); 348 | 349 | assert_eq!(get_trace_context(&headers), Some(context)); 350 | } 351 | 352 | #[test] 353 | fn trace_context_single() { 354 | let mut headers = HeaderMap::new(); 355 | let context = TraceContext::builder() 356 | .trace_id([0, 1, 2, 3, 4, 5, 6, 7].into()) 357 | .parent_id([1, 2, 3, 4, 5, 6, 7, 8].into()) 358 | .span_id([2, 3, 4, 5, 6, 7, 8, 9].into()) 359 | .sampled(true) 360 | .build(); 361 | set_trace_context_single(context, &mut headers); 362 | 363 | let mut expected_headers = HeaderMap::new(); 364 | expected_headers.insert( 365 | "b3", 366 | HeaderValue::from_static("0001020304050607-0203040506070809-1-0102030405060708"), 367 | ); 368 | assert_eq!(headers, expected_headers); 369 | 370 | assert_eq!(get_trace_context(&headers), Some(context)); 371 | } 372 | 373 | #[test] 374 | fn trace_context_unsampled_single() { 375 | let mut headers = HeaderMap::new(); 376 | let context = TraceContext::builder() 377 | .trace_id([0, 1, 2, 3, 4, 5, 6, 7].into()) 378 | .parent_id([1, 2, 3, 4, 5, 6, 7, 8].into()) 379 | .span_id([2, 3, 4, 5, 6, 7, 8, 9].into()) 380 | .build(); 381 | set_trace_context_single(context, &mut headers); 382 | 383 | let mut expected_headers = HeaderMap::new(); 384 | expected_headers.insert( 385 | "b3", 386 | HeaderValue::from_static("0001020304050607-0203040506070809-0102030405060708"), 387 | ); 388 | assert_eq!(headers, expected_headers); 389 | 390 | assert_eq!(get_trace_context(&headers), Some(context)); 391 | } 392 | 393 | #[test] 394 | fn trace_context_parentless_single() { 395 | let mut headers = HeaderMap::new(); 396 | let context = TraceContext::builder() 397 | .trace_id([0, 1, 2, 3, 4, 5, 6, 7].into()) 398 | .span_id([2, 3, 4, 5, 6, 7, 8, 9].into()) 399 | .sampled(true) 400 | .build(); 401 | set_trace_context_single(context, &mut headers); 402 | 403 | let mut expected_headers = HeaderMap::new(); 404 | expected_headers.insert( 405 | "b3", 406 | HeaderValue::from_static("0001020304050607-0203040506070809-1"), 407 | ); 408 | assert_eq!(headers, expected_headers); 409 | 410 | assert_eq!(get_trace_context(&headers), Some(context)); 411 | } 412 | 413 | #[test] 414 | fn trace_context_minimal_single() { 415 | let mut headers = HeaderMap::new(); 416 | let context = TraceContext::builder() 417 | .trace_id([0, 1, 2, 3, 4, 5, 6, 7].into()) 418 | .span_id([2, 3, 4, 5, 6, 7, 8, 9].into()) 419 | .build(); 420 | set_trace_context_single(context, &mut headers); 421 | 422 | let mut expected_headers = HeaderMap::new(); 423 | expected_headers.insert( 424 | "b3", 425 | HeaderValue::from_static("0001020304050607-0203040506070809"), 426 | ); 427 | assert_eq!(headers, expected_headers); 428 | 429 | assert_eq!(get_trace_context(&headers), Some(context)); 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /zipkin-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zipkin-macros" 3 | version.workspace = true 4 | authors = ["Steven Fackler "] 5 | edition = "2018" 6 | license = "Apache-2.0" 7 | description = "Macros for use with `zipkin`" 8 | repository = "https://github.com/palantir/rust-zipkin" 9 | readme = "../README.md" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | quote = "1.0" 16 | proc-macro2 = "1.0" 17 | syn = { version = "2.0", features = ["full"] } 18 | -------------------------------------------------------------------------------- /zipkin-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Palantir Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | //! Macros for use with `zipkin`. 15 | //! 16 | //! You should not depend on this crate directly. 17 | extern crate proc_macro; 18 | 19 | use proc_macro::TokenStream; 20 | use proc_macro2::Span; 21 | use quote::{quote, ToTokens}; 22 | use syn::parse::{Parse, ParseStream}; 23 | use syn::punctuated::Punctuated; 24 | use syn::{parse_macro_input, Error, Expr, ImplItemFn, Lit, LitStr, Meta, Stmt, Token}; 25 | 26 | /// Wraps the execution of a function or method in a span. 27 | /// 28 | /// Both normal and `async` methods and functions are supported. The name of the span is specified as an argument 29 | /// to the macro attribute. 30 | /// 31 | /// Requires the `macros` Cargo feature. 32 | /// 33 | /// # Examples 34 | /// 35 | /// ```ignore 36 | /// #[zipkin::spanned(name = "shave yaks")] 37 | /// fn shave_some_yaks(yaks: &mut [Yak]) { 38 | /// // ... 39 | /// } 40 | /// 41 | /// #[zipkin::spanned(name = "asynchronously shave yaks")] 42 | /// async fn shave_some_other_yaks(yaks: &mut [Yak]) { 43 | /// // ... 44 | /// } 45 | /// 46 | /// struct Yak; 47 | /// 48 | /// impl Yak { 49 | /// #[zipkin::spanned(name = "shave a yak")] 50 | /// fn shave(&mut self) { 51 | /// // ... 52 | /// } 53 | /// 54 | /// #[zipkin::spanned(name = "asynchronously shave a yak")] 55 | /// async fn shave_nonblocking(&mut self) { 56 | /// // ... 57 | /// } 58 | /// } 59 | /// ``` 60 | #[proc_macro_attribute] 61 | pub fn spanned(args: TokenStream, item: TokenStream) -> TokenStream { 62 | let options = parse_macro_input!(args as Options); 63 | let func = parse_macro_input!(item as ImplItemFn); 64 | 65 | spanned_impl(options, func).unwrap_or_else(|e| e.to_compile_error().into()) 66 | } 67 | 68 | fn spanned_impl(options: Options, mut func: ImplItemFn) -> Result { 69 | let name = &options.name; 70 | 71 | if func.sig.asyncness.is_some() { 72 | let stmts = &func.block.stmts; 73 | func.block.stmts = vec![ 74 | syn::parse2(quote! { 75 | let __macro_impl_span = zipkin::next_span() 76 | .with_name(#name) 77 | .detach(); 78 | }) 79 | .unwrap(), 80 | Stmt::Expr( 81 | syn::parse2(quote! { 82 | __macro_impl_span.bind(async move { #(#stmts)* }).await 83 | }) 84 | .unwrap(), 85 | None, 86 | ), 87 | ]; 88 | } else { 89 | let stmt = quote! { 90 | let __macro_impl_span = zipkin::next_span().with_name(#name); 91 | }; 92 | func.block.stmts.insert(0, syn::parse2(stmt).unwrap()); 93 | }; 94 | 95 | Ok(func.into_token_stream().into()) 96 | } 97 | 98 | struct Options { 99 | name: LitStr, 100 | } 101 | 102 | impl Parse for Options { 103 | fn parse(input: ParseStream) -> syn::Result { 104 | let args = Punctuated::::parse_terminated(input)?; 105 | 106 | let mut name = None; 107 | 108 | for arg in args { 109 | let meta = match arg { 110 | Meta::NameValue(meta) => meta, 111 | _ => return Err(Error::new_spanned(&arg, "invalid attribute syntax")), 112 | }; 113 | 114 | if meta.path.is_ident("name") { 115 | match meta.value { 116 | Expr::Lit(lit) => match lit.lit { 117 | Lit::Str(lit) => name = Some(lit), 118 | lit => return Err(Error::new_spanned(&lit, "expected a string literal")), 119 | }, 120 | _ => return Err(Error::new_spanned(meta, "expected `name = \"...\"`")), 121 | } 122 | } else { 123 | return Err(Error::new_spanned(meta.path, "unknown option")); 124 | } 125 | } 126 | 127 | Ok(Options { 128 | name: name.ok_or_else(|| Error::new(Span::call_site(), "missing `name` option"))?, 129 | }) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /zipkin-types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zipkin-types" 3 | version.workspace = true 4 | authors = ["Steven Fackler "] 5 | edition = "2018" 6 | license = "Apache-2.0" 7 | description = "Type definitions for Zipkin distributed trace information" 8 | repository = "https://github.com/palantir/rust-zipkin" 9 | readme = "../README.md" 10 | categories = ["network-programming", "web-programming"] 11 | keywords = ["zipkin", "tracing"] 12 | 13 | [dependencies] 14 | data-encoding = "2.1" 15 | serde = { version = "1.0", optional = true, features = ["derive"] } 16 | -------------------------------------------------------------------------------- /zipkin-types/src/annotation.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Palantir Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Annotations. 16 | use std::time::SystemTime; 17 | 18 | /// Associates an event that explains latency with a timestamp. 19 | /// 20 | /// Unlike log statements, annotations are often codes, e.g. "ws" for WireSend. 21 | /// 22 | /// Zipkin v1 core annotations such as "cs" and "sr" have been replaced with 23 | /// `Span::kind`, which interprets timestamp and duration. 24 | #[derive(Debug, Clone)] 25 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 26 | #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] 27 | pub struct Annotation { 28 | #[cfg_attr(feature = "serde", serde(with = "crate::time_micros"))] 29 | timestamp: SystemTime, 30 | value: String, 31 | } 32 | 33 | impl Annotation { 34 | /// Creates a new `Annotation`. 35 | #[inline] 36 | pub fn new(timestamp: SystemTime, value: &str) -> Annotation { 37 | Annotation { 38 | timestamp, 39 | value: value.to_string(), 40 | } 41 | } 42 | 43 | /// Creates a new `Annotation` at the current time. 44 | #[inline] 45 | pub fn now(value: &str) -> Annotation { 46 | Annotation::new(SystemTime::now(), value) 47 | } 48 | 49 | /// Returns the time at which the annotated event occurred. 50 | #[inline] 51 | pub fn timestamp(&self) -> SystemTime { 52 | self.timestamp 53 | } 54 | 55 | /// Returns the value of the annotation. 56 | #[inline] 57 | pub fn value(&self) -> &str { 58 | &self.value 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /zipkin-types/src/endpoint.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Palantir Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Endpoints. 16 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 17 | 18 | /// The network context of a node in the service graph. 19 | #[derive(Debug, Clone)] 20 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 21 | #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] 22 | pub struct Endpoint { 23 | #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] 24 | service_name: Option, 25 | #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] 26 | ipv4: Option, 27 | #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] 28 | ipv6: Option, 29 | #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] 30 | port: Option, 31 | } 32 | 33 | impl Endpoint { 34 | /// Returns a builder type used to construct an `Endpoint`. 35 | #[inline] 36 | pub fn builder() -> Builder { 37 | Builder { 38 | service_name: None, 39 | ipv4: None, 40 | ipv6: None, 41 | port: None, 42 | } 43 | } 44 | 45 | /// Returns the name of the service at this endpoint. 46 | #[inline] 47 | pub fn service_name(&self) -> Option<&str> { 48 | self.service_name.as_deref() 49 | } 50 | 51 | /// Returns the IPv4 address of the service at this endpoint. 52 | #[inline] 53 | pub fn ipv4(&self) -> Option { 54 | self.ipv4 55 | } 56 | 57 | /// Returns the IPv6 address of the service at this endpoint. 58 | #[inline] 59 | pub fn ipv6(&self) -> Option { 60 | self.ipv6 61 | } 62 | 63 | /// Returns the port of the service at this endpoint. 64 | #[inline] 65 | pub fn port(&self) -> Option { 66 | self.port 67 | } 68 | } 69 | 70 | /// A builder type for `Endpoint`s. 71 | pub struct Builder { 72 | service_name: Option, 73 | ipv4: Option, 74 | ipv6: Option, 75 | port: Option, 76 | } 77 | 78 | impl From for Builder { 79 | #[inline] 80 | fn from(e: Endpoint) -> Builder { 81 | Builder { 82 | service_name: e.service_name, 83 | ipv4: e.ipv4, 84 | ipv6: e.ipv6, 85 | port: e.port, 86 | } 87 | } 88 | } 89 | 90 | impl Builder { 91 | /// Sets the service name associated with the endpoint. 92 | /// 93 | /// Defaults to `None`. 94 | #[inline] 95 | pub fn service_name(&mut self, service_name: &str) -> &mut Builder { 96 | self.service_name = Some(service_name.to_string()); 97 | self 98 | } 99 | 100 | /// Sets the IPv4 address associated with the endpoint. 101 | /// 102 | /// Defaults to `None`. 103 | #[inline] 104 | pub fn ipv4(&mut self, ipv4: Ipv4Addr) -> &mut Builder { 105 | self.ipv4 = Some(ipv4); 106 | self 107 | } 108 | 109 | /// Sets the IPv6 address associated with the endpoint. 110 | /// 111 | /// Defaults to `None`. 112 | #[inline] 113 | pub fn ipv6(&mut self, ipv6: Ipv6Addr) -> &mut Builder { 114 | self.ipv6 = Some(ipv6); 115 | self 116 | } 117 | 118 | /// Sets the IP address associated with the endpoint. 119 | /// 120 | /// This is simply a convenience function which delegates to `ipv4` and 121 | /// `ipv6`. 122 | #[inline] 123 | pub fn ip(&mut self, ip: IpAddr) -> &mut Builder { 124 | match ip { 125 | IpAddr::V4(addr) => self.ipv4(addr), 126 | IpAddr::V6(addr) => self.ipv6(addr), 127 | } 128 | } 129 | 130 | /// Sets the port associated with the endpoint. 131 | /// 132 | /// Defaults to `None`. 133 | #[inline] 134 | pub fn port(&mut self, port: u16) -> &mut Builder { 135 | self.port = Some(port); 136 | self 137 | } 138 | 139 | /// Constructs the `Endpoint`. 140 | #[inline] 141 | pub fn build(&self) -> Endpoint { 142 | Endpoint { 143 | service_name: self.service_name.clone(), 144 | ipv4: self.ipv4, 145 | ipv6: self.ipv6, 146 | port: self.port, 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /zipkin-types/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Palantir Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Type definitions for Zipkin distributed trace information. 16 | //! 17 | //! This library corresponds to version 2 of the Zipkin [specification]. 18 | //! 19 | //! # Serialization 20 | //! 21 | //! If the `serde` Cargo feature is enabled, `Annotation`, `Endpoint`, `Kind`, `Span`, `SpanId`, and 22 | //! `TraceId` implement `Serialize` and `Deserialize` in the standard Zipkin format. 23 | //! 24 | //! [specification]: https://github.com/openzipkin/zipkin-api/blob/master/zipkin2-api.yaml 25 | #![doc(html_root_url = "https://docs.rs/zipkin-types/0.1")] 26 | #![warn(missing_docs)] 27 | 28 | #[doc(inline)] 29 | pub use crate::annotation::Annotation; 30 | #[doc(inline)] 31 | pub use crate::endpoint::Endpoint; 32 | #[doc(inline)] 33 | pub use crate::span::{Kind, Span}; 34 | #[doc(inline)] 35 | pub use crate::span_id::SpanId; 36 | #[doc(inline)] 37 | pub use crate::trace_id::TraceId; 38 | 39 | pub mod annotation; 40 | pub mod endpoint; 41 | pub mod span; 42 | pub mod span_id; 43 | pub mod trace_id; 44 | 45 | #[cfg(feature = "serde")] 46 | mod time_micros { 47 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 48 | use std::time::{Duration, SystemTime, UNIX_EPOCH}; 49 | 50 | pub fn to_wire(time: &SystemTime) -> u64 { 51 | super::duration_micros::to_wire( 52 | &time 53 | .duration_since(UNIX_EPOCH) 54 | .unwrap_or(Duration::from_secs(0)), 55 | ) 56 | } 57 | 58 | pub fn from_wire(time: u64) -> SystemTime { 59 | let duration = super::duration_micros::from_wire(time); 60 | UNIX_EPOCH + duration 61 | } 62 | 63 | pub fn serialize(time: &SystemTime, s: S) -> Result 64 | where 65 | S: Serializer, 66 | { 67 | to_wire(time).serialize(s) 68 | } 69 | 70 | pub fn deserialize<'de, D>(d: D) -> Result 71 | where 72 | D: Deserializer<'de>, 73 | { 74 | u64::deserialize(d).map(from_wire) 75 | } 76 | } 77 | 78 | #[cfg(feature = "serde")] 79 | mod duration_micros { 80 | use std::time::Duration; 81 | 82 | pub fn to_wire(duration: &Duration) -> u64 { 83 | let micros = duration.as_secs() * 1_000_000 + duration.subsec_nanos() as u64 / 1_000; 84 | micros.max(1) 85 | } 86 | 87 | pub fn from_wire(duration: u64) -> Duration { 88 | let seconds = duration / 1_000_000; 89 | let subsec_nanos = (duration % 1_000_000) * 1_000; 90 | Duration::new(seconds, subsec_nanos as u32) 91 | } 92 | } 93 | 94 | #[cfg(feature = "serde")] 95 | mod opt_time_micros { 96 | use serde::{Deserialize, Deserializer, Serializer}; 97 | use std::time::SystemTime; 98 | 99 | pub fn serialize(time: &Option, s: S) -> Result 100 | where 101 | S: Serializer, 102 | { 103 | match *time { 104 | Some(ref time) => s.serialize_some(&super::time_micros::to_wire(time)), 105 | None => s.serialize_none(), 106 | } 107 | } 108 | 109 | pub fn deserialize<'de, D>(d: D) -> Result, D::Error> 110 | where 111 | D: Deserializer<'de>, 112 | { 113 | Option::::deserialize(d).map(|o| o.map(super::time_micros::from_wire)) 114 | } 115 | } 116 | 117 | #[cfg(feature = "serde")] 118 | mod opt_duration_micros { 119 | use serde::{Deserialize, Deserializer, Serializer}; 120 | use std::time::Duration; 121 | 122 | pub fn serialize(duration: &Option, s: S) -> Result 123 | where 124 | S: Serializer, 125 | { 126 | match *duration { 127 | Some(ref duration) => s.serialize_some(&super::duration_micros::to_wire(duration)), 128 | None => s.serialize_none(), 129 | } 130 | } 131 | 132 | pub fn deserialize<'de, D>(d: D) -> Result, D::Error> 133 | where 134 | D: Deserializer<'de>, 135 | { 136 | Option::::deserialize(d).map(|o| o.map(super::duration_micros::from_wire)) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /zipkin-types/src/span.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Palantir Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Spans. 16 | use crate::{Annotation, Endpoint, SpanId, TraceId}; 17 | use std::collections::HashMap; 18 | use std::time::{Duration, SystemTime}; 19 | 20 | /// The "kind" of a span. 21 | /// 22 | /// This has an impact on the relationship between the span's timestamp, duration, and local 23 | /// endpoint. 24 | #[derive(Debug, Copy, Clone)] 25 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 26 | #[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))] 27 | #[non_exhaustive] 28 | pub enum Kind { 29 | /// The client side of an RPC. 30 | /// 31 | /// * Timestamp - The moment a request was sent (formerly "cs") 32 | /// * Duration - When present, indicates when a response was received (formerly "cr") 33 | /// * Remote Endpoint - Represents the server. 34 | Client, 35 | 36 | /// The server side of an RPC. 37 | /// 38 | /// * Timestamp - The moment a request was received (formerly "sr") 39 | /// * Duration - When present, indicates when a response was received (formerly "ss") 40 | /// * Remote Endpoint - Represents the client. 41 | Server, 42 | 43 | /// A message sent to a message broker. 44 | /// 45 | /// * Timestamp - The moment a message was sent to a destination (formerly "ms") 46 | /// * Duration - When present, represents the delay sending the message, such as batching. 47 | /// * Remote Endpoint - Represents the broker. 48 | Producer, 49 | 50 | /// A message received from a message broker. 51 | /// 52 | /// * Timestamp - The moment a message was received from an origin (formerly "mr") 53 | /// * Duration - When present, represents the delay consuming the message, such as from a 54 | /// backlog. 55 | /// * Remote Endpoint - Represents the broker. 56 | Consumer, 57 | } 58 | 59 | /// A `Span` represents a single operation over some range of time. 60 | /// 61 | /// Multiple spans make up a single "trace" of a distributed computation, and 62 | /// spans can be nested. A new trace is created with a "root" span, and 63 | /// subsections of that computation are recorded in individual spans. 64 | /// 65 | /// For spans tracing a remote service call, two records will typically be 66 | /// generated, one from the client and the other from the server. The client is 67 | /// responsible for recording the timestamp and duration associated with the 68 | /// span, and the server span should omit that information. The client and 69 | /// server may both add their own annotations and binary annotations the span - 70 | /// they will be merged. 71 | #[derive(Debug, Clone)] 72 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 73 | #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] 74 | pub struct Span { 75 | trace_id: TraceId, 76 | #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] 77 | name: Option, 78 | #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] 79 | parent_id: Option, 80 | id: SpanId, 81 | #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] 82 | kind: Option, 83 | #[cfg_attr( 84 | feature = "serde", 85 | serde( 86 | skip_serializing_if = "Option::is_none", 87 | with = "crate::opt_time_micros" 88 | ) 89 | )] 90 | timestamp: Option, 91 | #[cfg_attr( 92 | feature = "serde", 93 | serde( 94 | skip_serializing_if = "Option::is_none", 95 | with = "crate::opt_duration_micros" 96 | ) 97 | )] 98 | duration: Option, 99 | #[cfg_attr( 100 | feature = "serde", 101 | serde(skip_serializing_if = "is_false", default = "value_false") 102 | )] 103 | debug: bool, 104 | #[cfg_attr( 105 | feature = "serde", 106 | serde(skip_serializing_if = "is_false", default = "value_false") 107 | )] 108 | shared: bool, 109 | #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] 110 | local_endpoint: Option, 111 | #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] 112 | remote_endpoint: Option, 113 | #[cfg_attr( 114 | feature = "serde", 115 | serde(skip_serializing_if = "Vec::is_empty", default) 116 | )] 117 | annotations: Vec, 118 | #[cfg_attr( 119 | feature = "serde", 120 | serde(skip_serializing_if = "HashMap::is_empty", default) 121 | )] 122 | tags: HashMap, 123 | } 124 | 125 | #[cfg(feature = "serde")] 126 | #[inline] 127 | fn is_false(v: &bool) -> bool { 128 | !*v 129 | } 130 | 131 | #[cfg(feature = "serde")] 132 | #[inline] 133 | fn value_false() -> bool { 134 | false 135 | } 136 | 137 | impl Span { 138 | /// Returns a builder used to construct a `Span`. 139 | #[inline] 140 | pub fn builder() -> Builder { 141 | Builder { 142 | trace_id: None, 143 | name: None, 144 | parent_id: None, 145 | id: None, 146 | kind: None, 147 | timestamp: None, 148 | duration: None, 149 | debug: false, 150 | shared: false, 151 | local_endpoint: None, 152 | remote_endpoint: None, 153 | annotations: vec![], 154 | tags: HashMap::new(), 155 | } 156 | } 157 | 158 | /// The randomly generated, unique identifier for a trace, set on all spans within it. 159 | #[inline] 160 | pub fn trace_id(&self) -> TraceId { 161 | self.trace_id 162 | } 163 | 164 | /// The logical operation this span represents (e.g. an RPC method). 165 | /// 166 | /// Leave absent if unknown. 167 | /// 168 | /// These are lookup labels, so take care to ensure names are low cardinality. For example, do 169 | /// not embed variables into the name. 170 | #[inline] 171 | pub fn name(&self) -> Option<&str> { 172 | self.name.as_deref() 173 | } 174 | 175 | /// The parent span ID, or `None` if this is the root span in a trace. 176 | #[inline] 177 | pub fn parent_id(&self) -> Option { 178 | self.parent_id 179 | } 180 | 181 | /// The unique 64 bit identifier for this operation within the trace. 182 | #[inline] 183 | pub fn id(&self) -> SpanId { 184 | self.id 185 | } 186 | 187 | /// The "kind" of operation this span represents. 188 | /// 189 | /// When absent, the span is local or incomplete. 190 | #[inline] 191 | pub fn kind(&self) -> Option { 192 | self.kind 193 | } 194 | 195 | /// The start of the span. 196 | #[inline] 197 | pub fn timestamp(&self) -> Option { 198 | self.timestamp 199 | } 200 | 201 | /// The duration of the critical path, if known. 202 | /// 203 | /// Durations are recorded in microseconds, and rounded up to a minimum of 1. Durations of 204 | /// children can be longer than their parents due to asynchronous operations. 205 | #[inline] 206 | pub fn duration(&self) -> Option { 207 | self.duration 208 | } 209 | 210 | /// Determines if this span is part of a normal or forcibly sampled span. 211 | /// 212 | /// If true, the span should always be sampled regardless of the sampling configuration. 213 | #[inline] 214 | pub fn debug(&self) -> bool { 215 | self.debug 216 | } 217 | 218 | /// Determines if this span was started by another tracer (e.g. on a different host). 219 | #[inline] 220 | pub fn shared(&self) -> bool { 221 | self.shared 222 | } 223 | 224 | /// Returns the host that recorded this span, primarily for query by service name. 225 | /// 226 | /// Instrumentation should always record this. The IP address is usually the site local or 227 | /// advertised service address. When present, the port indicates the listen port. 228 | #[inline] 229 | pub fn local_endpoint(&self) -> Option<&Endpoint> { 230 | self.local_endpoint.as_ref() 231 | } 232 | 233 | /// Returns the other side of the connection for RPC or messaging spans. 234 | #[inline] 235 | pub fn remote_endpoint(&self) -> Option<&Endpoint> { 236 | self.remote_endpoint.as_ref() 237 | } 238 | 239 | /// Returns the annotations associated with this span. 240 | #[inline] 241 | pub fn annotations(&self) -> &[Annotation] { 242 | &self.annotations 243 | } 244 | 245 | /// Returns tags used to give spans context for search, viewing, and analysis. 246 | #[inline] 247 | pub fn tags(&self) -> &HashMap { 248 | &self.tags 249 | } 250 | } 251 | 252 | /// A builder for `Span`s. 253 | pub struct Builder { 254 | trace_id: Option, 255 | name: Option, 256 | parent_id: Option, 257 | id: Option, 258 | kind: Option, 259 | timestamp: Option, 260 | duration: Option, 261 | debug: bool, 262 | shared: bool, 263 | local_endpoint: Option, 264 | remote_endpoint: Option, 265 | annotations: Vec, 266 | tags: HashMap, 267 | } 268 | 269 | impl From for Builder { 270 | #[inline] 271 | fn from(s: Span) -> Builder { 272 | Builder { 273 | trace_id: Some(s.trace_id), 274 | name: s.name, 275 | parent_id: s.parent_id, 276 | id: Some(s.id), 277 | kind: s.kind, 278 | timestamp: s.timestamp, 279 | duration: s.duration, 280 | debug: s.debug, 281 | shared: s.shared, 282 | local_endpoint: s.local_endpoint, 283 | remote_endpoint: s.remote_endpoint, 284 | annotations: s.annotations, 285 | tags: s.tags, 286 | } 287 | } 288 | } 289 | 290 | impl Builder { 291 | /// Sets the trace ID of the span. 292 | #[inline] 293 | pub fn trace_id(&mut self, trace_id: TraceId) -> &mut Builder { 294 | self.trace_id = Some(trace_id); 295 | self 296 | } 297 | 298 | /// Sets the name of the span. 299 | /// 300 | /// Defaults to `None`. 301 | #[inline] 302 | pub fn name(&mut self, name: &str) -> &mut Builder { 303 | self.name = Some(name.to_lowercase()); 304 | self 305 | } 306 | 307 | /// Sets the ID of the span's parent. 308 | /// 309 | /// Defaults to `None`. 310 | #[inline] 311 | pub fn parent_id(&mut self, parent_id: SpanId) -> &mut Builder { 312 | self.parent_id = Some(parent_id); 313 | self 314 | } 315 | 316 | /// Sets the ID of the span. 317 | #[inline] 318 | pub fn id(&mut self, id: SpanId) -> &mut Builder { 319 | self.id = Some(id); 320 | self 321 | } 322 | 323 | /// Sets the kind of the span. 324 | /// 325 | /// Defaults to `None`. 326 | #[inline] 327 | pub fn kind(&mut self, kind: Kind) -> &mut Builder { 328 | self.kind = Some(kind); 329 | self 330 | } 331 | 332 | /// Sets the time of the beginning of the span. 333 | /// 334 | /// Defaults to `None`. 335 | #[inline] 336 | pub fn timestamp(&mut self, timestamp: SystemTime) -> &mut Builder { 337 | self.timestamp = Some(timestamp); 338 | self 339 | } 340 | 341 | /// Sets the duration of the span. 342 | /// 343 | /// Defaults to `None`. 344 | #[inline] 345 | pub fn duration(&mut self, duration: Duration) -> &mut Builder { 346 | self.duration = Some(duration); 347 | self 348 | } 349 | 350 | /// Sets the debug state of the span. 351 | /// 352 | /// Defaults to `false`. 353 | #[inline] 354 | pub fn debug(&mut self, debug: bool) -> &mut Builder { 355 | self.debug = debug; 356 | self 357 | } 358 | 359 | /// Sets the shared state of the span. 360 | /// 361 | /// Defaults to `false`. 362 | #[inline] 363 | pub fn shared(&mut self, shared: bool) -> &mut Builder { 364 | self.shared = shared; 365 | self 366 | } 367 | 368 | /// Sets the local endpoint of the span. 369 | /// 370 | /// Defaults to `None`. 371 | #[inline] 372 | pub fn local_endpoint(&mut self, local_endpoint: Endpoint) -> &mut Builder { 373 | self.local_endpoint = Some(local_endpoint); 374 | self 375 | } 376 | 377 | /// Sets the remote endpoint of the span. 378 | /// 379 | /// Defaults to `None`. 380 | #[inline] 381 | pub fn remote_endpoint(&mut self, remote_endpoint: Endpoint) -> &mut Builder { 382 | self.remote_endpoint = Some(remote_endpoint); 383 | self 384 | } 385 | 386 | /// Adds an annotation to the span. 387 | #[inline] 388 | pub fn annotation(&mut self, annotation: Annotation) -> &mut Builder { 389 | self.annotations.push(annotation); 390 | self 391 | } 392 | 393 | /// Adds multiple annotations to the span. 394 | #[inline] 395 | pub fn annotations(&mut self, annotations: I) -> &mut Builder 396 | where 397 | I: IntoIterator, 398 | { 399 | self.annotations.extend(annotations); 400 | self 401 | } 402 | 403 | /// Adds a tag to the span. 404 | #[inline] 405 | pub fn tag(&mut self, key: &str, value: &str) -> &mut Builder { 406 | self.tags.insert(key.to_string(), value.to_string()); 407 | self 408 | } 409 | 410 | /// As multiple tags to the span. 411 | #[inline] 412 | pub fn tags(&mut self, tags: I) -> &mut Builder 413 | where 414 | I: IntoIterator, 415 | { 416 | self.tags.extend(tags); 417 | self 418 | } 419 | 420 | /// Constructs a `Span`. 421 | /// 422 | /// # Panics 423 | /// 424 | /// Panics if `trace_id` or `id` was not set. 425 | #[inline] 426 | pub fn build(&self) -> Span { 427 | Span { 428 | trace_id: self.trace_id.expect("trace ID not set"), 429 | name: self.name.clone(), 430 | id: self.id.expect("span ID not set"), 431 | kind: self.kind, 432 | parent_id: self.parent_id, 433 | timestamp: self.timestamp, 434 | duration: self.duration, 435 | debug: self.debug, 436 | shared: self.shared, 437 | local_endpoint: self.local_endpoint.clone(), 438 | remote_endpoint: self.remote_endpoint.clone(), 439 | annotations: self.annotations.clone(), 440 | tags: self.tags.clone(), 441 | } 442 | } 443 | } 444 | -------------------------------------------------------------------------------- /zipkin-types/src/span_id.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Palantir Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Span IDs. 16 | use data_encoding::{DecodeError, HEXLOWER_PERMISSIVE}; 17 | use std::error::Error; 18 | use std::fmt; 19 | use std::str::FromStr; 20 | 21 | /// The ID of a span. 22 | /// 23 | /// Span IDs are 8 bytes, and are serialized as hexadecimal strings. 24 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 25 | pub struct SpanId { 26 | buf: [u8; 8], 27 | } 28 | 29 | impl FromStr for SpanId { 30 | type Err = SpanIdParseError; 31 | 32 | fn from_str(s: &str) -> Result { 33 | let mut buf = [0; 8]; 34 | match HEXLOWER_PERMISSIVE.decode_len(s.len()) { 35 | Ok(8) => { 36 | HEXLOWER_PERMISSIVE 37 | .decode_mut(s.as_bytes(), &mut buf) 38 | .map_err(|e| SpanIdParseError(Some(e.error)))?; 39 | } 40 | _ => return Err(SpanIdParseError(None)), 41 | } 42 | 43 | Ok(SpanId { buf }) 44 | } 45 | } 46 | 47 | impl fmt::Display for SpanId { 48 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 49 | for b in self.bytes() { 50 | write!(fmt, "{:02x}", b)?; 51 | } 52 | Ok(()) 53 | } 54 | } 55 | 56 | #[cfg(feature = "serde")] 57 | mod serde { 58 | use crate::span_id::SpanId; 59 | use serde::de::{Error, Unexpected, Visitor}; 60 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 61 | use std::fmt; 62 | 63 | impl Serialize for SpanId { 64 | fn serialize(&self, s: S) -> Result 65 | where 66 | S: Serializer, 67 | { 68 | s.collect_str(self) 69 | } 70 | } 71 | 72 | impl<'de> Deserialize<'de> for SpanId { 73 | fn deserialize(d: D) -> Result 74 | where 75 | D: Deserializer<'de>, 76 | { 77 | d.deserialize_str(V) 78 | } 79 | } 80 | 81 | struct V; 82 | 83 | impl<'de> Visitor<'de> for V { 84 | type Value = SpanId; 85 | 86 | fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 87 | fmt.write_str("a hex-encoded span ID") 88 | } 89 | 90 | fn visit_str(self, v: &str) -> Result 91 | where 92 | E: Error, 93 | { 94 | v.parse() 95 | .map_err(|_| Error::invalid_value(Unexpected::Str(v), &self)) 96 | } 97 | } 98 | } 99 | 100 | impl SpanId { 101 | /// Returns the bytes of the span ID. 102 | #[inline] 103 | pub fn bytes(&self) -> &[u8] { 104 | &self.buf 105 | } 106 | } 107 | 108 | impl From<[u8; 8]> for SpanId { 109 | #[inline] 110 | fn from(bytes: [u8; 8]) -> SpanId { 111 | SpanId { buf: bytes } 112 | } 113 | } 114 | 115 | /// The error returned when parsing a `SpanId` from a string. 116 | #[derive(Debug)] 117 | pub struct SpanIdParseError(Option); 118 | 119 | impl fmt::Display for SpanIdParseError { 120 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 121 | fmt.write_str("error parsing span: ")?; 122 | match self.0 { 123 | Some(ref err) => write!(fmt, "{}", err), 124 | None => fmt.write_str("invalid length"), 125 | } 126 | } 127 | } 128 | 129 | impl Error for SpanIdParseError { 130 | fn source(&self) -> Option<&(dyn Error + 'static)> { 131 | self.0.as_ref().map(|e| e as _) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /zipkin-types/src/trace_id.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Palantir Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Trace IDs. 16 | use data_encoding::{DecodeError, HEXLOWER_PERMISSIVE}; 17 | use std::error::Error; 18 | use std::fmt; 19 | use std::str::FromStr; 20 | 21 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 22 | enum Inner { 23 | Short([u8; 8]), 24 | Long([u8; 16]), 25 | } 26 | 27 | /// The ID of a trace. 28 | /// 29 | /// Trace IDs are either 8 or 16 bytes, and are serialized as hexadecimal 30 | /// strings. 31 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 32 | pub struct TraceId(Inner); 33 | 34 | impl fmt::Display for TraceId { 35 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | for b in self.bytes() { 37 | write!(fmt, "{:02x}", b)?; 38 | } 39 | Ok(()) 40 | } 41 | } 42 | 43 | impl FromStr for TraceId { 44 | type Err = TraceIdParseError; 45 | 46 | fn from_str(s: &str) -> Result { 47 | let inner = match HEXLOWER_PERMISSIVE.decode_len(s.len()) { 48 | Ok(8) => { 49 | let mut buf = [0; 8]; 50 | HEXLOWER_PERMISSIVE 51 | .decode_mut(s.as_bytes(), &mut buf) 52 | .map_err(|e| TraceIdParseError(Some(e.error)))?; 53 | Inner::Short(buf) 54 | } 55 | Ok(16) => { 56 | let mut buf = [0; 16]; 57 | HEXLOWER_PERMISSIVE 58 | .decode_mut(s.as_bytes(), &mut buf) 59 | .map_err(|e| TraceIdParseError(Some(e.error)))?; 60 | Inner::Long(buf) 61 | } 62 | _ => return Err(TraceIdParseError(None)), 63 | }; 64 | 65 | Ok(TraceId(inner)) 66 | } 67 | } 68 | 69 | #[cfg(feature = "serde")] 70 | mod serde { 71 | use crate::trace_id::TraceId; 72 | use serde::de::{Error, Unexpected, Visitor}; 73 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 74 | use std::fmt; 75 | 76 | impl Serialize for TraceId { 77 | fn serialize(&self, s: S) -> Result 78 | where 79 | S: Serializer, 80 | { 81 | s.collect_str(self) 82 | } 83 | } 84 | 85 | impl<'de> Deserialize<'de> for TraceId { 86 | fn deserialize(d: D) -> Result 87 | where 88 | D: Deserializer<'de>, 89 | { 90 | d.deserialize_str(V) 91 | } 92 | } 93 | 94 | struct V; 95 | 96 | impl<'de> Visitor<'de> for V { 97 | type Value = TraceId; 98 | 99 | fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 100 | fmt.write_str("a hex-encoded trace ID") 101 | } 102 | 103 | fn visit_str(self, v: &str) -> Result 104 | where 105 | E: Error, 106 | { 107 | v.parse() 108 | .map_err(|_| Error::invalid_value(Unexpected::Str(v), &self)) 109 | } 110 | } 111 | } 112 | 113 | impl TraceId { 114 | /// Returns the byte representation of the trace ID. 115 | #[inline] 116 | pub fn bytes(&self) -> &[u8] { 117 | match self.0 { 118 | Inner::Short(ref buf) => buf, 119 | Inner::Long(ref buf) => buf, 120 | } 121 | } 122 | } 123 | 124 | impl From<[u8; 8]> for TraceId { 125 | #[inline] 126 | fn from(bytes: [u8; 8]) -> TraceId { 127 | TraceId(Inner::Short(bytes)) 128 | } 129 | } 130 | 131 | impl From<[u8; 16]> for TraceId { 132 | #[inline] 133 | fn from(bytes: [u8; 16]) -> TraceId { 134 | TraceId(Inner::Long(bytes)) 135 | } 136 | } 137 | 138 | /// The error returned when parsing a `TraceId` from a string. 139 | #[derive(Debug)] 140 | pub struct TraceIdParseError(Option); 141 | 142 | impl fmt::Display for TraceIdParseError { 143 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 144 | fmt.write_str("error parsing trace ID: ")?; 145 | match self.0 { 146 | Some(ref err) => write!(fmt, "{}", err), 147 | None => fmt.write_str("invalid length"), 148 | } 149 | } 150 | } 151 | 152 | impl Error for TraceIdParseError { 153 | fn source(&self) -> Option<&(dyn Error + 'static)> { 154 | self.0.as_ref().map(|e| e as _) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /zipkin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zipkin" 3 | version.workspace = true 4 | authors = ["Steven Fackler "] 5 | edition = "2018" 6 | license = "Apache-2.0" 7 | description = "A library for collecting timing information about distributed computations" 8 | repository = "https://github.com/palantir/rust-zipkin" 9 | readme = "../README.md" 10 | categories = ["network-programming", "web-programming"] 11 | keywords = ["zipkin", "tracing"] 12 | 13 | [package.metadata.docs.rs] 14 | all-features = true 15 | 16 | [features] 17 | serde = ["zipkin-types/serde"] 18 | macros = ["zipkin-macros"] 19 | 20 | [dependencies] 21 | log = "0.4" 22 | lazycell = "1.0" 23 | pin-project-lite = "0.2" 24 | rand = "0.9" 25 | 26 | zipkin-macros = { version = "1.0.0", optional = true, path = "../zipkin-macros" } 27 | zipkin-types = { version = "1.0.0", path = "../zipkin-types" } 28 | 29 | [dev-dependencies] 30 | futures = "0.3" 31 | -------------------------------------------------------------------------------- /zipkin/src/current.rs: -------------------------------------------------------------------------------- 1 | use crate::TraceContext; 2 | use std::cell::Cell; 3 | use std::marker::PhantomData; 4 | 5 | thread_local! { 6 | static CURRENT: Cell> = const { Cell::new(None) }; 7 | } 8 | 9 | /// A guard object for the thread-local current trace context. 10 | /// 11 | /// It will restore the previous trace context when it drops. 12 | pub struct CurrentGuard { 13 | prev: Option, 14 | // make sure this type is !Send since it pokes at thread locals 15 | _p: PhantomData<*const ()>, 16 | } 17 | 18 | unsafe impl Sync for CurrentGuard {} 19 | 20 | impl Drop for CurrentGuard { 21 | fn drop(&mut self) { 22 | CURRENT.with(|c| c.set(self.prev)); 23 | } 24 | } 25 | 26 | /// Sets this thread's current trace context. 27 | /// 28 | /// This method does not start a span. It is designed to be used when 29 | /// propagating the trace of an existing span to a new thread. 30 | /// 31 | /// A guard object is returned which will restore the previous trace context 32 | /// when it falls out of scope. 33 | pub fn set_current(context: TraceContext) -> CurrentGuard { 34 | CurrentGuard { 35 | prev: CURRENT.with(|c| c.replace(Some(context))), 36 | _p: PhantomData, 37 | } 38 | } 39 | 40 | /// Returns this thread's current trace context. 41 | pub fn current() -> Option { 42 | CURRENT.with(|c| c.get()) 43 | } 44 | -------------------------------------------------------------------------------- /zipkin/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Palantir Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Zipkin is a library for collecting timing information about computations in 16 | //! distributed systems. This information is collected into [Zipkin][] spans. 17 | //! 18 | //! This library corresponds to version 2 of the Zipkin [specification]. 19 | //! 20 | //! # Serialization 21 | //! 22 | //! If the `serde` Cargo feature is enabled, `Annotation`, `Endpoint`, `Kind`, `Span`, `SpanId`, and 23 | //! `TraceId` implement `Serialize` and `Deserialize` in the standard Zipkin format. 24 | //! 25 | //! [Zipkin]: http://zipkin.io/ 26 | //! [specification]: https://github.com/openzipkin/zipkin-api/blob/master/zipkin2-api.yaml 27 | #![doc(html_root_url = "https://docs.rs/zipkin/0.4")] 28 | #![warn(missing_docs)] 29 | 30 | #[cfg(feature = "macros")] 31 | pub use zipkin_macros::*; 32 | #[doc(inline)] 33 | pub use zipkin_types::{ 34 | annotation, endpoint, span, span_id, trace_id, Annotation, Endpoint, Kind, Span, SpanId, 35 | TraceId, 36 | }; 37 | 38 | #[doc(inline)] 39 | pub use crate::current::*; 40 | #[doc(inline)] 41 | pub use crate::open_span::*; 42 | #[doc(inline)] 43 | pub use crate::report::Report; 44 | #[doc(inline)] 45 | pub use crate::sample::Sample; 46 | #[doc(inline)] 47 | pub use crate::sampling_flags::SamplingFlags; 48 | #[doc(inline)] 49 | pub use crate::trace_context::TraceContext; 50 | #[doc(inline)] 51 | pub use crate::tracer::*; 52 | 53 | mod current; 54 | mod open_span; 55 | pub mod report; 56 | pub mod sample; 57 | pub mod sampling_flags; 58 | pub mod trace_context; 59 | mod tracer; 60 | 61 | #[cfg(test)] 62 | mod test; 63 | -------------------------------------------------------------------------------- /zipkin/src/open_span.rs: -------------------------------------------------------------------------------- 1 | use crate::{span, tracer, Annotation, CurrentGuard, Endpoint, Kind, TraceContext}; 2 | use pin_project_lite::pin_project; 3 | use std::future::Future; 4 | use std::mem; 5 | use std::pin::Pin; 6 | use std::task::{Context, Poll}; 7 | use std::time::Instant; 8 | 9 | /// A type indicating that an `OpenSpan` is "attached" to the current thread. 10 | pub struct Attached { 11 | _guard: CurrentGuard, 12 | } 13 | 14 | /// A type indicating that an `OpenSpan` is "detached" from the current thread. 15 | pub struct Detached(()); 16 | 17 | #[allow(clippy::large_enum_variant)] 18 | pub(crate) enum SpanState { 19 | Real { 20 | span: span::Builder, 21 | start_instant: Instant, 22 | }, 23 | Nop, 24 | } 25 | 26 | /// An open span. 27 | /// 28 | /// This is a guard object - the span will be finished and reported when it 29 | /// falls out of scope. 30 | /// 31 | /// Spans can either be "attached" to or "detached" from their tracer. An attached span manages the 32 | /// thread's current span - it acts like a `CurrentGuard`. A detached span does not but is `Send` 33 | /// unlike an attached span. Spans are attached by default, but can be detached or reattached via 34 | /// the `detach` and `attach` methods. 35 | /// 36 | /// Detached spans are intended for use when you need to manually maintain the current trace 37 | /// context. For example, when working with nonblocking futures a single OS thread is managing many 38 | /// separate tasks. The `bind` method binds a span to a future, setting the thread's current span 39 | /// each time the thread is polled. If some computation starts executing on one thread and finishes 40 | /// executing on another, you can detach the span, send it to the other thread, and then reattach 41 | /// it to properly model that behavior. 42 | pub struct OpenSpan { 43 | _mode: T, 44 | context: TraceContext, 45 | state: SpanState, 46 | } 47 | 48 | impl Drop for OpenSpan { 49 | fn drop(&mut self) { 50 | if let SpanState::Real { 51 | span, 52 | start_instant, 53 | } = &mut self.state 54 | { 55 | if let Some(tracer) = tracer::TRACER.borrow() { 56 | let span = span.duration(start_instant.elapsed()).build(); 57 | tracer.reporter.report(span); 58 | } 59 | } 60 | } 61 | } 62 | 63 | impl OpenSpan { 64 | /// Returns the context associated with this span. 65 | #[inline] 66 | pub fn context(&self) -> TraceContext { 67 | self.context 68 | } 69 | 70 | /// Sets the name of this span. 71 | #[inline] 72 | pub fn name(&mut self, name: &str) { 73 | if let SpanState::Real { span, .. } = &mut self.state { 74 | span.name(name); 75 | } 76 | } 77 | 78 | /// A builder-style version of `name`. 79 | #[inline] 80 | pub fn with_name(mut self, name: &str) -> OpenSpan { 81 | self.name(name); 82 | self 83 | } 84 | 85 | /// Sets the kind of this span. 86 | #[inline] 87 | pub fn kind(&mut self, kind: Kind) { 88 | if let SpanState::Real { span, .. } = &mut self.state { 89 | span.kind(kind); 90 | } 91 | } 92 | 93 | /// A builder-style version of `kind`. 94 | #[inline] 95 | pub fn with_kind(mut self, kind: Kind) -> OpenSpan { 96 | self.kind(kind); 97 | self 98 | } 99 | 100 | /// Sets the remote endpoint of this span. 101 | #[inline] 102 | pub fn remote_endpoint(&mut self, remote_endpoint: Endpoint) { 103 | if let SpanState::Real { span, .. } = &mut self.state { 104 | span.remote_endpoint(remote_endpoint); 105 | } 106 | } 107 | 108 | /// A builder-style version of `remote_endpoint`. 109 | #[inline] 110 | pub fn with_remote_endpoint(mut self, remote_endpoint: Endpoint) -> OpenSpan { 111 | self.remote_endpoint(remote_endpoint); 112 | self 113 | } 114 | 115 | /// Attaches an annotation to this span. 116 | #[inline] 117 | pub fn annotate(&mut self, value: &str) { 118 | if let SpanState::Real { span, .. } = &mut self.state { 119 | let annotation = Annotation::now(value); 120 | span.annotation(annotation); 121 | } 122 | } 123 | 124 | /// A builder-style version of `annotate`. 125 | #[inline] 126 | pub fn with_annotation(mut self, value: &str) -> OpenSpan { 127 | self.annotate(value); 128 | self 129 | } 130 | 131 | /// Attaches a tag to this span. 132 | #[inline] 133 | pub fn tag(&mut self, key: &str, value: &str) { 134 | if let SpanState::Real { span, .. } = &mut self.state { 135 | span.tag(key, value); 136 | } 137 | } 138 | 139 | /// A builder-style version of `tag`. 140 | #[inline] 141 | pub fn with_tag(mut self, key: &str, value: &str) -> OpenSpan { 142 | self.tag(key, value); 143 | self 144 | } 145 | } 146 | 147 | impl OpenSpan { 148 | #[inline] 149 | pub(crate) fn new(context: TraceContext, state: SpanState) -> OpenSpan { 150 | OpenSpan { 151 | _mode: Attached { 152 | _guard: crate::set_current(context), 153 | }, 154 | context, 155 | state, 156 | } 157 | } 158 | 159 | /// Detaches this span's context from the tracer. 160 | #[inline] 161 | pub fn detach(mut self) -> OpenSpan { 162 | OpenSpan { 163 | _mode: Detached(()), 164 | context: self.context, 165 | // since we've swapped in Nop here, self's Drop impl won't do anything 166 | state: mem::replace(&mut self.state, SpanState::Nop), 167 | } 168 | } 169 | } 170 | 171 | impl OpenSpan { 172 | /// Re-attaches this span's context to the tracer. 173 | #[inline] 174 | pub fn attach(mut self) -> OpenSpan { 175 | OpenSpan { 176 | _mode: Attached { 177 | _guard: crate::set_current(self.context), 178 | }, 179 | context: self.context, 180 | // since we've swapped in Nop here, self's Drop impl won't do anything 181 | state: mem::replace(&mut self.state, SpanState::Nop), 182 | } 183 | } 184 | 185 | /// Binds this span to a future. 186 | /// 187 | /// Returns a new future which sets the span's context as the current when polled before 188 | /// delegating to the inner future. The span will close when the future is dropped. 189 | #[inline] 190 | pub fn bind(self, future: F) -> Bind 191 | where 192 | F: Future, 193 | { 194 | Bind { 195 | span: Some(self), 196 | future, 197 | } 198 | } 199 | } 200 | 201 | pin_project! { 202 | /// A type which wraps a future, associating it with an `OpenSpan`. 203 | /// 204 | /// The span's context will be set as the current whenever it's polled, and the span will close 205 | /// when the future completes. 206 | pub struct Bind { 207 | span: Option>, 208 | #[pin] 209 | future: T, 210 | } 211 | } 212 | 213 | impl Future for Bind 214 | where 215 | T: Future, 216 | { 217 | type Output = T::Output; 218 | 219 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 220 | let this = self.project(); 221 | let _guard = crate::set_current( 222 | this.span 223 | .as_ref() 224 | .expect("future polled after completion") 225 | .context(), 226 | ); 227 | 228 | let r = this.future.poll(cx); 229 | if r.is_ready() { 230 | *this.span = None; 231 | } 232 | 233 | r 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /zipkin/src/report.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Palantir Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Span reporters. 16 | use crate::Span; 17 | use log::info; 18 | use std::sync::Arc; 19 | 20 | /// A reporter consumes Zipkin spans and reports them. 21 | /// 22 | /// For example, the reporter may log the span information to a file, or send 23 | /// it over the network to a collection service. 24 | pub trait Report { 25 | /// Reports a span. 26 | fn report(&self, span: Span); 27 | } 28 | 29 | impl Report for Arc 30 | where 31 | T: ?Sized + Report, 32 | { 33 | fn report(&self, span: Span) { 34 | (**self).report(span) 35 | } 36 | } 37 | 38 | impl Report for Box 39 | where 40 | T: ?Sized + Report, 41 | { 42 | fn report(&self, span: Span) { 43 | (**self).report(span) 44 | } 45 | } 46 | 47 | /// A `Report`er which does nothing. 48 | pub struct NopReporter; 49 | 50 | impl Report for NopReporter { 51 | fn report(&self, _: Span) {} 52 | } 53 | 54 | /// A `Report`er which logs the `Span` at the `info` level. 55 | /// 56 | /// The `Span` is simply logged in its `Debug` representation which is not 57 | /// stable, so this reporter is only useful for testing. 58 | pub struct LoggingReporter; 59 | 60 | impl Report for LoggingReporter { 61 | fn report(&self, span: Span) { 62 | info!("{:?}", span); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /zipkin/src/sample.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Palantir Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Span samplers. 16 | use crate::TraceId; 17 | use std::sync::Arc; 18 | 19 | /// A sampler decides whether or not a span should be recorded based on its 20 | /// trace ID. 21 | /// 22 | /// A trace context received from a remote service may already indicate if the 23 | /// span should be recorded, but if it does not, a `Sample`r is responsible for 24 | /// making that decision. 25 | pub trait Sample { 26 | /// Returns `true` if the span associated with the trace ID should be 27 | /// recorded. 28 | fn sample(&self, trace_id: TraceId) -> bool; 29 | } 30 | 31 | impl Sample for Arc 32 | where 33 | T: ?Sized + Sample, 34 | { 35 | fn sample(&self, trace_id: TraceId) -> bool { 36 | (**self).sample(trace_id) 37 | } 38 | } 39 | 40 | impl Sample for Box 41 | where 42 | T: ?Sized + Sample, 43 | { 44 | fn sample(&self, trace_id: TraceId) -> bool { 45 | (**self).sample(trace_id) 46 | } 47 | } 48 | 49 | /// A `Sample`r which always returns `true`. 50 | pub struct AlwaysSampler; 51 | 52 | impl Sample for AlwaysSampler { 53 | fn sample(&self, _: TraceId) -> bool { 54 | true 55 | } 56 | } 57 | 58 | /// A `Sample`r which always returns `false`. 59 | pub struct NeverSampler; 60 | 61 | impl Sample for NeverSampler { 62 | fn sample(&self, _: TraceId) -> bool { 63 | false 64 | } 65 | } 66 | 67 | /// A `Sample`r which randomly samples at a specific rate. 68 | pub struct RandomSampler { 69 | rate: f32, 70 | } 71 | 72 | impl RandomSampler { 73 | /// Creates a new `RandomSampler` at the specified rate. 74 | /// 75 | /// # Panics 76 | /// 77 | /// Panics if `rate` is less than 0 or greater than 1. 78 | pub fn new(rate: f32) -> RandomSampler { 79 | assert!((0. ..=1.).contains(&rate)); 80 | RandomSampler { rate } 81 | } 82 | } 83 | 84 | impl Sample for RandomSampler { 85 | fn sample(&self, _: TraceId) -> bool { 86 | rand::random::() < self.rate 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /zipkin/src/sampling_flags.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Palantir Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Sampling flags. 16 | 17 | /// Flags used to control sampling. 18 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 19 | pub struct SamplingFlags { 20 | sampled: Option, 21 | debug: bool, 22 | } 23 | 24 | impl Default for SamplingFlags { 25 | #[inline] 26 | fn default() -> SamplingFlags { 27 | SamplingFlags::builder().build() 28 | } 29 | } 30 | 31 | impl SamplingFlags { 32 | /// Returns a builder used to construct `SamplingFlags`. 33 | #[inline] 34 | pub fn builder() -> Builder { 35 | Builder { 36 | sampled: None, 37 | debug: false, 38 | } 39 | } 40 | 41 | /// Determines if sampling has been requested for this context. 42 | /// 43 | /// A value of `None` indicates that the service working in the context is 44 | /// responsible for determining if it should be sampled. 45 | #[inline] 46 | pub fn sampled(self) -> Option { 47 | self.sampled 48 | } 49 | 50 | /// Determines if this context is in debug mode. 51 | /// 52 | /// Debug contexts should always be sampled, regardless of the value of 53 | /// `sampled()`. 54 | #[inline] 55 | pub fn debug(self) -> bool { 56 | self.debug 57 | } 58 | } 59 | 60 | /// A builder type for `SamplingFlags`. 61 | pub struct Builder { 62 | sampled: Option, 63 | debug: bool, 64 | } 65 | 66 | impl From for Builder { 67 | #[inline] 68 | fn from(flags: SamplingFlags) -> Builder { 69 | Builder { 70 | sampled: flags.sampled, 71 | debug: flags.debug, 72 | } 73 | } 74 | } 75 | 76 | impl Builder { 77 | /// Sets the sampling request for this context. 78 | /// 79 | /// Defaults to `None`. 80 | #[inline] 81 | pub fn sampled(&mut self, sampled: bool) -> &mut Builder { 82 | self.sampled = Some(sampled); 83 | self 84 | } 85 | 86 | /// Sets the debug flag for this request. 87 | /// 88 | /// Defaults to `false`. 89 | #[inline] 90 | pub fn debug(&mut self, debug: bool) -> &mut Builder { 91 | self.debug = debug; 92 | self 93 | } 94 | 95 | /// Constructs `SamplingFlags`. 96 | #[inline] 97 | pub fn build(&self) -> SamplingFlags { 98 | SamplingFlags { 99 | sampled: if self.debug { Some(true) } else { self.sampled }, 100 | debug: self.debug, 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /zipkin/src/test/macros.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Palantir Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | use crate as zipkin; // hack to get the macro codegen to work in the same crate 15 | use crate::{spanned, test}; 16 | use futures::executor; 17 | 18 | fn is_send(_: T) 19 | where 20 | T: Send, 21 | { 22 | } 23 | 24 | #[test] 25 | fn blocking_free_function() { 26 | #[spanned(name = "foobar")] 27 | fn foo() { 28 | zipkin::next_span().with_name("fizzbuzz"); 29 | } 30 | 31 | test::init(); 32 | 33 | let span = zipkin::next_span().with_name("root"); 34 | foo(); 35 | drop(span); 36 | 37 | let spans = test::take(); 38 | assert_eq!(spans.len(), 3); 39 | assert_eq!(spans[0].name(), Some("fizzbuzz")); 40 | assert_eq!(spans[1].name(), Some("foobar")); 41 | assert_eq!(spans[2].name(), Some("root")); 42 | assert_eq!(spans[0].trace_id(), spans[2].trace_id()); 43 | assert_eq!(spans[1].trace_id(), spans[2].trace_id()); 44 | assert_eq!(spans[0].parent_id(), Some(spans[1].id())); 45 | assert_eq!(spans[1].parent_id(), Some(spans[2].id())); 46 | assert_eq!(spans[2].parent_id(), None); 47 | } 48 | 49 | #[test] 50 | fn blocking_associated_function() { 51 | struct Foo; 52 | 53 | impl Foo { 54 | #[spanned(name = "foobar")] 55 | fn foo() { 56 | zipkin::next_span().with_name("fizzbuzz"); 57 | } 58 | } 59 | 60 | test::init(); 61 | 62 | let span = zipkin::next_span().with_name("root"); 63 | Foo::foo(); 64 | drop(span); 65 | 66 | let spans = test::take(); 67 | assert_eq!(spans.len(), 3); 68 | assert_eq!(spans[0].name(), Some("fizzbuzz")); 69 | assert_eq!(spans[1].name(), Some("foobar")); 70 | assert_eq!(spans[2].name(), Some("root")); 71 | assert_eq!(spans[0].trace_id(), spans[2].trace_id()); 72 | assert_eq!(spans[1].trace_id(), spans[2].trace_id()); 73 | assert_eq!(spans[0].parent_id(), Some(spans[1].id())); 74 | assert_eq!(spans[1].parent_id(), Some(spans[2].id())); 75 | assert_eq!(spans[2].parent_id(), None); 76 | } 77 | 78 | #[test] 79 | fn blocking_method() { 80 | struct Foo; 81 | 82 | impl Foo { 83 | #[spanned(name = "foobar")] 84 | fn foo(&self) { 85 | zipkin::next_span().with_name("fizzbuzz"); 86 | } 87 | } 88 | 89 | test::init(); 90 | 91 | let span = zipkin::next_span().with_name("root"); 92 | Foo.foo(); 93 | drop(span); 94 | 95 | let spans = test::take(); 96 | assert_eq!(spans.len(), 3); 97 | assert_eq!(spans[0].name(), Some("fizzbuzz")); 98 | assert_eq!(spans[1].name(), Some("foobar")); 99 | assert_eq!(spans[2].name(), Some("root")); 100 | assert_eq!(spans[0].trace_id(), spans[2].trace_id()); 101 | assert_eq!(spans[1].trace_id(), spans[2].trace_id()); 102 | assert_eq!(spans[0].parent_id(), Some(spans[1].id())); 103 | assert_eq!(spans[1].parent_id(), Some(spans[2].id())); 104 | assert_eq!(spans[2].parent_id(), None); 105 | } 106 | 107 | #[test] 108 | fn async_free_function() { 109 | #[spanned(name = "foobar")] 110 | async fn foo() { 111 | zipkin::next_span().with_name("fizzbuzz"); 112 | } 113 | 114 | is_send(foo()); 115 | 116 | test::init(); 117 | 118 | let future = zipkin::next_span().with_name("root").detach().bind(foo()); 119 | executor::block_on(future); 120 | 121 | let spans = test::take(); 122 | assert_eq!(spans.len(), 3); 123 | assert_eq!(spans[0].name(), Some("fizzbuzz")); 124 | assert_eq!(spans[1].name(), Some("foobar")); 125 | assert_eq!(spans[2].name(), Some("root")); 126 | assert_eq!(spans[0].trace_id(), spans[2].trace_id()); 127 | assert_eq!(spans[1].trace_id(), spans[2].trace_id()); 128 | assert_eq!(spans[0].parent_id(), Some(spans[1].id())); 129 | assert_eq!(spans[1].parent_id(), Some(spans[2].id())); 130 | assert_eq!(spans[2].parent_id(), None); 131 | } 132 | 133 | #[test] 134 | fn async_associated_function() { 135 | struct Foo; 136 | 137 | impl Foo { 138 | #[spanned(name = "foobar")] 139 | async fn foo() { 140 | zipkin::next_span().with_name("fizzbuzz"); 141 | } 142 | } 143 | 144 | is_send(Foo::foo()); 145 | 146 | test::init(); 147 | 148 | let future = zipkin::next_span() 149 | .with_name("root") 150 | .detach() 151 | .bind(Foo::foo()); 152 | executor::block_on(future); 153 | 154 | let spans = test::take(); 155 | assert_eq!(spans.len(), 3); 156 | assert_eq!(spans[0].name(), Some("fizzbuzz")); 157 | assert_eq!(spans[1].name(), Some("foobar")); 158 | assert_eq!(spans[2].name(), Some("root")); 159 | assert_eq!(spans[0].trace_id(), spans[2].trace_id()); 160 | assert_eq!(spans[1].trace_id(), spans[2].trace_id()); 161 | assert_eq!(spans[0].parent_id(), Some(spans[1].id())); 162 | assert_eq!(spans[1].parent_id(), Some(spans[2].id())); 163 | assert_eq!(spans[2].parent_id(), None); 164 | } 165 | 166 | #[test] 167 | fn async_method() { 168 | struct Foo; 169 | 170 | impl Foo { 171 | #[spanned(name = "foobar")] 172 | async fn foo(&self) { 173 | zipkin::next_span().with_name("fizzbuzz"); 174 | } 175 | } 176 | 177 | is_send(Foo.foo()); 178 | 179 | test::init(); 180 | 181 | let future = zipkin::next_span() 182 | .with_name("root") 183 | .detach() 184 | .bind(Foo.foo()); 185 | executor::block_on(future); 186 | 187 | let spans = test::take(); 188 | assert_eq!(spans.len(), 3); 189 | assert_eq!(spans[0].name(), Some("fizzbuzz")); 190 | assert_eq!(spans[1].name(), Some("foobar")); 191 | assert_eq!(spans[2].name(), Some("root")); 192 | assert_eq!(spans[0].trace_id(), spans[2].trace_id()); 193 | assert_eq!(spans[1].trace_id(), spans[2].trace_id()); 194 | assert_eq!(spans[0].parent_id(), Some(spans[1].id())); 195 | assert_eq!(spans[1].parent_id(), Some(spans[2].id())); 196 | assert_eq!(spans[2].parent_id(), None); 197 | } 198 | -------------------------------------------------------------------------------- /zipkin/src/test/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Palantir Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | use crate::sample::AlwaysSampler; 15 | use crate::{Endpoint, Report, Span}; 16 | use futures::executor; 17 | use std::cell::RefCell; 18 | use std::mem; 19 | 20 | #[cfg(feature = "macros")] 21 | mod macros; 22 | 23 | thread_local! { 24 | static SPANS: RefCell> = const { RefCell::new(vec![]) }; 25 | } 26 | 27 | struct TestReporter; 28 | 29 | impl Report for TestReporter { 30 | fn report(&self, span: Span) { 31 | SPANS.with(|s| s.borrow_mut().push(span)); 32 | } 33 | } 34 | 35 | fn init() { 36 | let _ = crate::set_tracer(AlwaysSampler, TestReporter, Endpoint::builder().build()); 37 | SPANS.with(|s| s.borrow_mut().clear()); 38 | } 39 | 40 | fn take() -> Vec { 41 | SPANS.with(|s| mem::take(&mut *s.borrow_mut())) 42 | } 43 | 44 | #[test] 45 | fn detach_attach() { 46 | init(); 47 | 48 | let parent = crate::new_trace(); 49 | let parent_trace = parent.context().trace_id(); 50 | let parent_id = parent.context().span_id(); 51 | 52 | let detached = crate::next_span().detach(); 53 | let detached_id = detached.context().span_id(); 54 | 55 | let child2 = crate::next_span(); 56 | let child2_trace = child2.context().trace_id(); 57 | let child2_id = child2.context().span_id(); 58 | 59 | let attached = detached.attach(); 60 | let child3 = crate::next_span(); 61 | let child3_id = child3.context().span_id(); 62 | 63 | drop(child3); 64 | drop(attached); 65 | drop(child2); 66 | drop(parent); 67 | 68 | let spans = take(); 69 | assert_eq!(spans.len(), 4); 70 | assert_eq!(spans[0].id(), child3_id); 71 | assert_eq!(spans[0].parent_id(), Some(detached_id)); 72 | assert_eq!(spans[1].id(), detached_id); 73 | assert_eq!(spans[1].parent_id(), Some(parent_id)); 74 | assert_eq!(spans[2].trace_id(), child2_trace); 75 | assert_eq!(spans[2].id(), child2_id); 76 | assert_eq!(spans[2].parent_id(), Some(parent_id)); 77 | assert_eq!(spans[3].trace_id(), parent_trace); 78 | assert_eq!(spans[3].id(), parent_id); 79 | assert_eq!(spans[3].parent_id(), None); 80 | } 81 | 82 | #[test] 83 | fn bind() { 84 | init(); 85 | 86 | let future_root = crate::next_span(); 87 | let future_root_context = future_root.context(); 88 | 89 | let future = async { 90 | let span = crate::next_span(); 91 | span.context() 92 | }; 93 | 94 | let future = future_root.detach().bind(future); 95 | 96 | let other_root = crate::next_span(); 97 | let other_root_context = other_root.context(); 98 | 99 | let future_context = executor::block_on(future); 100 | 101 | drop(other_root); 102 | 103 | let spans = take(); 104 | assert_eq!(spans.len(), 3); 105 | assert_eq!(spans[0].id(), future_context.span_id()); 106 | assert_eq!(spans[0].parent_id(), Some(future_root_context.span_id())); 107 | assert_eq!(spans[1].id(), future_root_context.span_id()); 108 | assert_eq!(spans[1].parent_id(), None); 109 | assert_eq!(spans[2].id(), other_root_context.span_id()); 110 | assert_eq!(spans[2].parent_id(), None); 111 | } 112 | -------------------------------------------------------------------------------- /zipkin/src/trace_context.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Palantir Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Trace contexts. 16 | use crate::sampling_flags; 17 | use crate::{SamplingFlags, SpanId, TraceId}; 18 | 19 | /// A `TraceContext` represents a distributed trace request. 20 | /// 21 | /// It consists of a trace ID, the ID of the parent span, the ID of the 22 | /// context's span, and flags dealing with the sampling of the span. 23 | /// 24 | /// The trace context is sent to remote services on requests. For example, 25 | /// it is included in a standard set of headers in HTTP requests. 26 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 27 | pub struct TraceContext { 28 | trace_id: TraceId, 29 | parent_id: Option, 30 | span_id: SpanId, 31 | flags: SamplingFlags, 32 | } 33 | 34 | impl TraceContext { 35 | /// Returns a builder used to construct a `TraceContext`. 36 | #[inline] 37 | pub fn builder() -> Builder { 38 | Builder { 39 | trace_id: None, 40 | parent_id: None, 41 | span_id: None, 42 | flags: SamplingFlags::builder(), 43 | } 44 | } 45 | 46 | /// Returns the ID of the trace associated with this context. 47 | #[inline] 48 | pub fn trace_id(&self) -> TraceId { 49 | self.trace_id 50 | } 51 | 52 | /// Returns the ID of the parent of the span associated with this context. 53 | #[inline] 54 | pub fn parent_id(&self) -> Option { 55 | self.parent_id 56 | } 57 | 58 | /// Returns the ID of the span associated with this context. 59 | #[inline] 60 | pub fn span_id(&self) -> SpanId { 61 | self.span_id 62 | } 63 | 64 | /// Returns the sampling flags associated with this context. 65 | #[inline] 66 | pub fn sampling_flags(&self) -> SamplingFlags { 67 | self.flags 68 | } 69 | 70 | /// Determines if sampling has been requested for this context. 71 | /// 72 | /// A value of `None` indicates that the service working in the context is 73 | /// responsible for determining if it should be sampled. 74 | #[inline] 75 | pub fn sampled(&self) -> Option { 76 | self.flags.sampled() 77 | } 78 | 79 | /// Determines if this context is in debug mode. 80 | /// 81 | /// Debug contexts should always be sampled, regardless of the value of 82 | /// `sampled()`. 83 | #[inline] 84 | pub fn debug(&self) -> bool { 85 | self.flags.debug() 86 | } 87 | } 88 | 89 | /// A builder type for `TraceContext`s. 90 | pub struct Builder { 91 | trace_id: Option, 92 | parent_id: Option, 93 | span_id: Option, 94 | flags: sampling_flags::Builder, 95 | } 96 | 97 | impl From for Builder { 98 | #[inline] 99 | fn from(c: TraceContext) -> Builder { 100 | Builder { 101 | trace_id: Some(c.trace_id), 102 | parent_id: c.parent_id, 103 | span_id: Some(c.span_id), 104 | flags: c.flags.into(), 105 | } 106 | } 107 | } 108 | 109 | impl Builder { 110 | /// Sets the trace ID of this context. 111 | #[inline] 112 | pub fn trace_id(&mut self, trace_id: TraceId) -> &mut Builder { 113 | self.trace_id = Some(trace_id); 114 | self 115 | } 116 | 117 | /// Sets the ID of the parent span of this context. 118 | /// 119 | /// Defaults to `None`. 120 | #[inline] 121 | pub fn parent_id(&mut self, parent_id: SpanId) -> &mut Builder { 122 | self.parent_id = Some(parent_id); 123 | self 124 | } 125 | 126 | /// Sets the ID of the span of this context. 127 | #[inline] 128 | pub fn span_id(&mut self, span_id: SpanId) -> &mut Builder { 129 | self.span_id = Some(span_id); 130 | self 131 | } 132 | 133 | /// Sets the sampling flags for this context. 134 | #[inline] 135 | pub fn sampling_flags(&mut self, flags: SamplingFlags) -> &mut Builder { 136 | self.flags = flags.into(); 137 | self 138 | } 139 | 140 | /// Sets the sampling request for this context. 141 | /// 142 | /// Defaults to `None`. 143 | #[inline] 144 | pub fn sampled(&mut self, sampled: bool) -> &mut Builder { 145 | self.flags.sampled(sampled); 146 | self 147 | } 148 | 149 | /// Sets the debug flag for this request. 150 | /// 151 | /// Defaults to `false`. 152 | #[inline] 153 | pub fn debug(&mut self, debug: bool) -> &mut Builder { 154 | self.flags.debug(debug); 155 | self 156 | } 157 | 158 | /// Constructs a `TraceContext`. 159 | /// 160 | /// # Panics 161 | /// 162 | /// Panics if `trace_id` or `span_id` was not set. 163 | #[inline] 164 | pub fn build(&self) -> TraceContext { 165 | TraceContext { 166 | trace_id: self.trace_id.expect("trace ID not set"), 167 | parent_id: self.parent_id, 168 | span_id: self.span_id.expect("span ID not set"), 169 | flags: self.flags.build(), 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /zipkin/src/tracer.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Palantir Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Tracers. 16 | use crate::trace_context; 17 | use crate::{ 18 | Attached, Endpoint, OpenSpan, Report, Sample, SamplingFlags, Span, SpanId, SpanState, 19 | TraceContext, TraceId, 20 | }; 21 | use lazycell::AtomicLazyCell; 22 | use rand::Rng; 23 | use std::error::Error; 24 | use std::fmt; 25 | use std::time::{Instant, SystemTime}; 26 | 27 | pub(crate) static TRACER: AtomicLazyCell = AtomicLazyCell::NONE; 28 | 29 | pub(crate) struct Tracer { 30 | pub sampler: Box, 31 | pub reporter: Box, 32 | pub local_endpoint: Endpoint, 33 | } 34 | 35 | /// Initializes the global tracer. 36 | /// 37 | /// The tracer can only be initialized once in the lifetime of a program. Spans created before this function is called 38 | /// will be no-ops. 39 | /// 40 | /// Returns an error if the tracer is already initialized. 41 | pub fn set_tracer( 42 | sampler: S, 43 | reporter: R, 44 | local_endpoint: Endpoint, 45 | ) -> Result<(), SetTracerError> 46 | where 47 | S: Sample + 'static + Sync + Send, 48 | R: Report + 'static + Sync + Send, 49 | { 50 | TRACER 51 | .fill(Tracer { 52 | sampler: Box::new(sampler), 53 | reporter: Box::new(reporter), 54 | local_endpoint, 55 | }) 56 | .map_err(|_| SetTracerError(())) 57 | } 58 | 59 | /// The error returned when attempting to set a tracer when one is already installed. 60 | #[derive(Debug)] 61 | pub struct SetTracerError(()); 62 | 63 | impl fmt::Display for SetTracerError { 64 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 65 | fmt.write_str("") 66 | } 67 | } 68 | 69 | impl Error for SetTracerError {} 70 | 71 | /// Starts a new trace. 72 | pub fn new_trace() -> OpenSpan { 73 | new_trace_from(SamplingFlags::default()) 74 | } 75 | 76 | /// Stats a new trace with specific sampling flags. 77 | pub fn new_trace_from(flags: SamplingFlags) -> OpenSpan { 78 | let id = next_id(); 79 | let context = TraceContext::builder() 80 | .trace_id(TraceId::from(id)) 81 | .span_id(SpanId::from(id)) 82 | .sampling_flags(flags) 83 | .build(); 84 | make_span(context, false) 85 | } 86 | 87 | /// Joins an existing trace. 88 | /// 89 | /// The context can come from, for example, the headers of an HTTP request. 90 | pub fn join_trace(context: TraceContext) -> OpenSpan { 91 | make_span(context, true) 92 | } 93 | 94 | /// Stats a new span with the specified parent. 95 | pub fn new_child(parent: TraceContext) -> OpenSpan { 96 | let id = next_id(); 97 | let context = TraceContext::builder() 98 | .trace_id(parent.trace_id()) 99 | .parent_id(parent.span_id()) 100 | .span_id(SpanId::from(id)) 101 | .sampling_flags(parent.sampling_flags()) 102 | .build(); 103 | make_span(context, false) 104 | } 105 | 106 | /// Creates a new span parented to the current one if it exists, or starting a new trace otherwise. 107 | pub fn next_span() -> OpenSpan { 108 | match crate::current() { 109 | Some(context) => new_child(context), 110 | None => new_trace(), 111 | } 112 | } 113 | 114 | fn next_id() -> [u8; 8] { 115 | let mut id = [0; 8]; 116 | rand::rng().fill(&mut id); 117 | id 118 | } 119 | 120 | fn make_span(mut context: TraceContext, mut shared: bool) -> OpenSpan { 121 | let tracer = match TRACER.borrow() { 122 | Some(tracer) => tracer, 123 | None => return OpenSpan::new(context, SpanState::Nop), 124 | }; 125 | 126 | if context.sampled().is_none() { 127 | context = trace_context::Builder::from(context) 128 | .sampled(tracer.sampler.sample(context.trace_id())) 129 | .build(); 130 | // since the thing we got the context from didn't indicate if it should be sampled, 131 | // we can't assume they're recording the span as well. 132 | shared = false; 133 | } 134 | 135 | let state = match context.sampled() { 136 | Some(false) => SpanState::Nop, 137 | _ => { 138 | let mut span = Span::builder(); 139 | span.trace_id(context.trace_id()) 140 | .id(context.span_id()) 141 | .timestamp(SystemTime::now()) 142 | .shared(shared) 143 | .local_endpoint(tracer.local_endpoint.clone()); 144 | 145 | if let Some(parent_id) = context.parent_id() { 146 | span.parent_id(parent_id); 147 | } 148 | 149 | SpanState::Real { 150 | span, 151 | start_instant: Instant::now(), 152 | } 153 | } 154 | }; 155 | 156 | OpenSpan::new(context, state) 157 | } 158 | --------------------------------------------------------------------------------