├── .github └── workflows │ └── self_test.yml ├── .gitignore ├── .pr-preview.json ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── W3CTRMANIFEST ├── errata.html ├── http_header_format_rationale.md ├── implementations.md ├── index.html ├── spec ├── 01-abstract.md ├── 02-sotd.md ├── 10-overview.md ├── 20-http_request_header_format.md ├── 21-http_response_header_format.md ├── 30-processing-model.md ├── 40-other-protocols.md ├── 50-privacy.md ├── 51-security.md ├── 60-acknowledgments.md ├── 60-trace-id-format.md └── 61-span-id-format.md ├── test ├── .gitignore ├── README.md ├── client.py ├── self_test.py ├── server.py ├── test.py └── tracecontext │ ├── __init__.py │ ├── test_traceparent.py │ ├── test_tracestate.py │ ├── traceparent.py │ └── tracestate.py └── w3c.json /.github/workflows/self_test.yml: -------------------------------------------------------------------------------- 1 | name: Self Test 2 | on: 3 | push: 4 | pull_request: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | name: Run the python tests 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Setup python 13 | uses: actions/setup-python@v3 14 | with: 15 | python-version: 3.11.2 16 | architecture: x64 17 | - run: pip install aiohttp 18 | - run: cd test && python self_test.py 19 | shell: bash 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ IDEA 2 | .idea 3 | *.iml 4 | 5 | # Eclipse 6 | .classpath 7 | .project 8 | .settings 9 | bin 10 | 11 | # NetBeans 12 | /.nb-gradle 13 | /.nb-gradle-properties 14 | 15 | # OS X 16 | .DS_Store 17 | 18 | # Emacs 19 | *~ 20 | \#*\# 21 | 22 | # Vim 23 | .swp -------------------------------------------------------------------------------- /.pr-preview.json: -------------------------------------------------------------------------------- 1 | { 2 | "src_file": "index.html", 3 | "type": "respec" 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrap.wrappingColumn": [80,120], 3 | "editor.rulers": [80,120], 4 | "markdownlint.config": { 5 | "MD013": true 6 | }, 7 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | All documentation, code and communication under this repository are covered by 4 | the [W3C Code of Ethics and Professional 5 | Conduct](https://www.w3.org/Consortium/cepc/). 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing into Distributed Trace Context 2 | 3 | This repository is being used for work in the [W3C Distributed Trace Context 4 | Working Group](https://www.w3.org/community/trace-context/). 5 | 6 | Contributions to this repository are intended to become part of 7 | Recommendation-track documents governed by the [W3C Patent 8 | Policy](https://www.w3.org/Consortium/Patent-Policy/) and [Software and Document 9 | License](https://www.w3.org/Consortium/Legal/copyright-software). To make 10 | substantive contributions to specifications, you must either participate in the 11 | relevant W3C Working Group or make a non-member patent licensing commitment. 12 | 13 | If you are not the sole contributor to a contribution (pull request), please 14 | identify all contributors in the pull request comment. 15 | 16 | To add a contributor (other than yourself, that's automatic), mark them one per 17 | line as follows: 18 | 19 | ``` bash 20 | +@github_username 21 | ``` 22 | 23 | If you added a contributor by mistake, you can remove them in a comment with: 24 | 25 | ``` bash 26 | -@github_username 27 | ``` 28 | 29 | If you are making a pull request on behalf of someone else but you had no part 30 | in designing the feature, you can remove yourself with the above syntax. 31 | 32 | ## Editing report 33 | 34 | This repository follows the best practices from https://w3c.github.io/ 35 | 36 | - Report is generated by [ReSpec](https://github.com/w3c/respec/wiki). 37 | - We highly encourage to use line breaks in markdown files at 80 characters 38 | wide. There are tools that can do it for you effectively. Please submit 39 | proposal to include your editor settings required to enable this behavior so 40 | the out of the box settings for this repository will be consistent. 41 | - To view report locally - start the local server like `python -m 42 | SimpleHTTPServer` to avoid cross-site file loading issue 43 | - You can preview report from your branch or fork 44 | `http://htmlpreview.github.io/?https://github.com//trace-context/blob//report-trace-context.html`. 45 | It is useful to review PR suggestions. 46 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | All Reports in this Repository are licensed by contributors 2 | under the 3 | [W3C Software and Document License](https://www.w3.org/Consortium/Legal/copyright-software). 4 | 5 | All tests in this Repository are licensed by contributors to be distributed under the 6 | [W3C 3-clause BSD License](https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html). 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build 2 | Status](https://img.shields.io/travis/w3c/trace-context/main.svg?label=validation%20service)](https://travis-ci.com/w3c/trace-context/) 3 | 4 | # Trace Context Specification 5 | 6 | This repository is associated with the [Trace 7 | Context](https://www.w3.org/TR/trace-context/) specification, 8 | which specifies a distributed tracing context propagation format. 9 | 10 | [Trace Context v1](https://www.w3.org/TR/trace-context-1/) has [W3C Recommendation](https://www.w3.org/2019/Process-20190301/#rec-publication) status. 11 | 12 | The next version of specification is being developed in this repository. 13 | 14 | The current draft of the Trace Context Specification from this repository's main branch is published to https://w3c.github.io/trace-context/. 15 | Explanations and justification for decisions made in this specification are written down in the [Rationale document](http_header_format_rationale.md). 16 | 17 | ## Team Communication 18 | 19 | See 20 | [communication](https://github.com/w3c/distributed-tracing-wg#team-communication) 21 | 22 | We appreciate feedback and contributions. Please make sure to read rationale 23 | documents when you have a question about particular decision made in 24 | specification. 25 | 26 | ## Goal 27 | 28 | This specification defines formats to pass trace context information across 29 | systems. Our goal is to share this with the community so that various tracing 30 | and diagnostics products can operate together. 31 | 32 | ## Reference Implementations 33 | 34 | There are few open source and commercial implementations of this trace context specification 35 | available. 36 | 37 | A simplistic regex-based implementation can be found in the `test` folder. This 38 | implementation has 100% compliance to the test suite. 39 | 40 | .NET Framework will ship trace context specification support in the upcoming 41 | version. See 42 | [Activity](https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs) 43 | for implementation details. 44 | 45 | A list of all currently implementations can be found [here](./implementations.md). 46 | 47 | ## Why are we doing this 48 | 49 | See [Why](https://github.com/w3c/distributed-tracing-wg#why-are-we-doing-this) 50 | 51 | ## Contributing 52 | 53 | See [Contributing.md](CONTRIBUTING.md) for details. 54 | -------------------------------------------------------------------------------- /W3CTRMANIFEST: -------------------------------------------------------------------------------- 1 | index.html?specStatus=WD&shortName=trace-context respec 2 | -------------------------------------------------------------------------------- /errata.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | Open Errata for Trace Context 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 |
22 | 23 |
24 |

Open Errata for Trace Context

25 |
26 |
Latest errata update:
27 |
28 |
Number of recorded errata:
29 |
30 |
Link to all errata:
31 |
32 |
33 | 34 |
35 |

How to Submit an Erratum?

36 |

Errata are introduced and stored in the issue list of the group‘s GitHub repository. The workflow to add a new erratum is as follows:

37 |
    38 |
  • An issue is raised for a possible erratum. The label of the issue SHOULD be set to “ErratumRaised”. It SHOULD also include the label corresponding to the document on which the erratum is raised, i.e., “trace-context-1”. (Please, consult the list of available labels.) It is o.k. for an erratum to have several labels. In some, exceptional, cases, i.e., when the erratum is very general, it is also acceptable not to have a reference to a document.
  • 39 |
  • Issues labeled as “Editorial” are displayed separately, to make it easier to differentiate editorial errata from substantial ones.
  • 40 |
  • The community discusses the issue. If it is accepted as a genuine erratum, the label “Errata” is added to the entry and the “ErratumRaised” label should be removed. Additionally, a new comment on the issue MAY be added, beginning with the word "Summary:" (if such a summary is useful based on the discussion).
  • 41 |
  • If the community rejects the issue as an erratum, the issue should be closed.
  • 42 |
  • Each errata may be labelled as “Editorial”; editorial errata are listed separately from the substantial ones.
  • 43 | 46 |
47 | 48 |

This report contains a reference to all open issues with the label Errata, displayed in the sections below. Each section collects the issues for a specific document, with a separate section for the issues not assigned to any.

49 | 50 |
51 |
52 | 53 |
54 | 55 |
56 | 57 |
58 |

Open Errata on the “Trace Context - Level 1” Recommendation

59 |
60 |
Latest Published Version:
61 |
https://www.w3.org/TR/trace-context-1/
62 |
Editor’s draft:
63 |
https://w3c.github.io/trace-context
64 |
Latest Publication Date:
65 |
6 February, 2020
66 |
67 |
68 |

Substantial Issues

69 |
70 |
71 |

Editorial Issues

72 |
73 |
74 | 75 |
76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /http_header_format_rationale.md: -------------------------------------------------------------------------------- 1 | # Trace context HTTP header format rationale 2 | 3 | This document provides rationale for the decisions made, mapping the `traceparent` and `tracestate` fields to HTTP headers. 4 | 5 | ## Lowercase concatenated header names 6 | 7 | While HTTP headers are conventionally delimited by hyphens, the trace context header names are not. Rather, they are lowercase concatenated `traceparent` and `tracestate` respectively. The departure from convention is due to practical concerns of propagation. Trace context is unlike typical HTTP headers, which are point-to-point and do not propagate through other systems like messaging. Different systems have different constraints. For example, some cannot read case insensitively, and others forbid the hyphen character. Even if we could suggest not using the same format for such systems, we know many systems transparently copy HTTP headers into fields. This class of concerns only exist when we choose to support mixed case with hyphens. By choosing not to, we open trace context integration beyond HTTP at the cost of a conventional distraction. 8 | 9 | ## All parts of `traceparent` are required 10 | 11 | We've been discussing to make parts of the `traceparent` header optional. One proposal we declined was to allow trace-id-only `traceparent` headers. The intended use was to save size for small clients (like mobile devices) initiating the call. The rationale for declining it was to avoid abuse and confusion. A suggestion that we want to discuss on saving size is to use binary format. 12 | 13 | Making `trace-flags` optional doesn't save a lot, but makes specification more complicated. And it potentially can lead to incompatible implementations which do not expect `trace-flags`. 14 | 15 | ## Span/parent nomenclature 16 | 17 | We were using the term `span-id` in the `traceparent`, but not all tracing systems are built around span model, e.g. X-Trace, Canopy, SolarWinds, are built around event model, which is considered more expressive than the span model. There is nothing in the spec actually requires the model to be span-based, and passing the ID of the happened-before "thing" should work for both types of trace models. We considered names `call-id`, `request-id`. However out of all replacements `parent-id` is probably the best name. First, it matched the header name. Second it indicates a difference between caller and callee. Discussing AMQP we realized that `message-id` header defined by AMQP refers to individual message, and semantically not the same as traceparent. Message id can be used to dedup messages on the server when traceparent only defines the source this message came from. 18 | 19 | ## Ordering of keys in `tracestate` 20 | 21 | The specification calls for ordering of values in tracestate. This requirement allows better interoperability between tracing vendors. 22 | 23 | A typical distributed trace is clustered - components calling each other are often monitored by the same tracing vendor. So information supplied by the tracing system which originated a request will typically be less and less important deeper in a distributed trace. Immediate caller's information on the other hand typically is more valuable as it is more likely being monitored by the same tracing vendor. Thus, it is logical to move immediate caller's information to the beginning of the `tracestate` list. So less important values will be pushed to the end of the list. 24 | 25 | This prioritization of `tracestate` values improves performance of querying the value of tracestate - typically you only need a first pair. It also allows you to meaningfully truncate `tracestate` when required instead of dropping the entire list of values. 26 | 27 | ## Mutations of `tracestate` 28 | 29 | Two questions that comes up frequently is whether the `tracestate` header HAVE TO be mutated on every mutation of `parent-id` to identify the vendor which made this change and whether two different vendors can modify the `tracestate` entries in a single component. 30 | 31 | This requirement may improve interoperability between vendors. For instance, a vendor may check the first `tracestate` key and provide some additional value for the customer by adjusting data collection in the current component via the knowledge of a caller's behavior. For instance, applying specific sampling policies or providing an experience for customers to get data from the caller's vendor. There are more scenarios that might be simplified by strict mutation requirements. 32 | 33 | Even though improved interoperability will enable more scenarios, the specification does not restrict the number of mutations of `tracestate` and doesn't require the mutation. 34 | 35 | The main reason for not requiring the mutation is generic tracers. Generic tracers are tracers which don't need to carry any information via `tracestate` and/or don't have a single back-end where this data will be stored. The only thing a generic tracer can set in `tracestate` is either a key with some constant, an empty value or a copy of `traceparent`. Neither of those details is particularly interesting for the callee. But a requirement puts an extra burden and complexity on implementors. Another reason for not requiring a mutation is that allowing multiple mutations may require vendors to check for more than one key anyway. 36 | 37 | Some back-end neutral SDKs may be implemented so that destination back-end is decided via side-car or out-of-process agent configuration. In such cases a customer may decide to enable more than one headers' mutation logic in in-process SDK. Another requirement for multiple mutations is that in a similar environment where the back-end destination is decided via out-of-process configuration - certain header mutations may still be required. An example may be smart sampling mechanisms that rely on additional data propagation in `tracestate`. 38 | 39 | ## Size limits of `tracestate` 40 | 41 | ### Total size limit 42 | 43 | The field `tracestate` opens up rich interoperability and extensibility scenarios for vendors. There are defined use cases for the `tracestate` like positioning request in multiple distributed tracing graphs and providing backward compatibility with older protocols. But `tracestate` is not limited to this use and will drive innovations and new scenarios going forward. 44 | 45 | The opaque nature of `tracestate` introduces some tension between guarantees to vendors around propagating vendor-specific data with traces vs. storage and propagation constraints. 46 | 47 | On the one hand, field value should be small so implementors can satisfy the requirement to pass the value all the time. It is especially critical for various messaging systems where metadata like `tracestate` is not paid for by the customer and provided for free. 48 | 49 | On the other hand, in order for the field to be useful there should be some guarantees that it will in fact be propagated in most of the cases. 50 | 51 | Without changing definition of `tracestate` to make it less opaque, any declared limit will be arbitrary. It may be based on discussions with vendors on their needs. But it's still arbitrary as - first, it may not account for the needs of everybody and second - whatever guarantee will be provided - it will be abused as guaranteed propagation is a very tempting and hard to implement feature. 52 | 53 | Talking about abuse - one thing this working group is doing is working on user-defined context propagation as part of [Correlation Context](https://w3c.github.io/correlation-context/) spec. Correlation context will provide a relieve valve for people who want to have freedom propagating relatively big payload across the components of a distributed trace. 54 | 55 | This specification allows removing and adding as many `tracestate` entries as an implementation needs. This freedom is required to satisfy privacy and interoperability concerns. Thus all suggestions specification is making regarding the size of `tracestate` field will only be a recommendation that would improve vendors interoperability and cannot be "enforced" in practice. 56 | 57 | Three solutions to address this problem were discussed: 58 | 59 | 1. Declare the arbitrary max length that HAVE TO be propagated. 60 | 2. Limit based on number of entries, not the size. Declare arbitrary minimum number of entries required for propagation. 61 | 3. Make max length the decision of the implementor. Suggest arbitrary max length in specification that implementors SHOULD propagate. 62 | 63 | These solutions has the following pros and cons. 64 | 65 | #### 1. Declare the arbitrary max length 66 | 67 | This is simplest and easy to understand solution. Proposals for the limit varied from `20` (size of a single `parent-id` with small identifier) to `1024` to fit up to 5 vendors with large tracestate entries. `512` is a nice median length. 68 | 69 | Extremely small proposal like `64` wasn't received well by vendors, while larger limits was unacceptable/undesirable for cloud vendors as costs of implementing it are quite high. 70 | 71 | We failed to find a number everybody liked. 72 | 73 | #### 2. Limit based on number of entries 74 | 75 | This proposal was made to eliminate the problem of a "greedy" vendor utilizes the full length of a `tracestate` and ultimately blocking the interoperability. 76 | 77 | Problem with this proposal is that limits proposed for the individual entry (e.g. `128`) were still quite high for cloud providers. 78 | 79 | Also even though seemingly this proposal makes interoperability better, abuse of using multiple entries by a single vendor is still unavoidable. 80 | 81 | Good way to discourage vendors to use long `tracestate` values is to suggest to remove those first when limit was reached. 82 | 83 | #### 3. Make max length the decision of the implementor 84 | 85 | Options above "protected" vendors from implementors who will take optional nature of `tracestate` and will not propagate `tracestate`. However the reality is that for many platforms even larger `tracestate` fields propagation is not a concern. 86 | 87 | So there was a proposal that the spec will suggest the length limits, and define the algorithm of trimming `tracestate`. However the final decision of an actual implementation to the platform. 88 | 89 | One of the side effects of this proposal is that it encourages tracing vendors to minimize the use of `tracestate` for non-essential scenarios to ensure propagation of an essential fields. Which is aligned with the spirit of `tracestate`. 90 | 91 | One addition to this scenario was made to discourage people using `tracestate` values of size larger than `128` long. Specification suggests to cut those entries first. 92 | 93 | ### Maximum number of elements 94 | 95 | Here are some rationals and assumptions: 96 | 97 | - the total size can be calculated 2 * num_elements - 1 (delimiters) + sum(key.size()) + sum(value.size()). 98 | - we assume that each key will have around 4 elements (e.g. `msft`, `goog`, etc). 99 | - we assume that each value will have 8 or more characters (e.g. one hex int32). 100 | - based on the previous two assumptions each key-value pair will have more than 12 characters. 101 | 102 | Based on these assumptions and rationals a maximum number of elements of 32 looks like a reasonable compromise. 103 | 104 | ## Forcing lower case tracestate names 105 | 106 | Lowercase names have a few benefits: 107 | - consistent with structured headers specification http://httpwg.org/http-extensions/draft-ietf-httpbis-header-structure.html 108 | - make naming consistent and avoid potential interoperability issues between systems 109 | - encourages minimizing the name size to a single word 110 | 111 | ## String encoding of names 112 | 113 | Url encoding is low-overhead way to encode unicode characters for non-latin characters in the values. Url encoding keeps a single words in latin unchanged and easily readable. 114 | 115 | ## Vendor name in a key 116 | 117 | Sign `@` is allowed in a key for easy parsing of vendor name out of the tracestate key. The idea is that with the registry of tracing vendors one can easily understand the vendor name and how to parse it's tracestate. Without `@` sign parsing will be more complicated. Also `@` sign has known semantics in addressing for protocols like ftp and e-mails. 118 | 119 | ## Versioning 120 | 121 | Versioning options are: 122 | 123 | 1. Pass thru unknown headers 124 | 2. Re-start trace when you see unknown header 125 | 3. Try to parse trace following some rules when you see unknown header 126 | 127 | One variation is whether original or new header that you cannot recognize is preserved in `tracestate`. 128 | 129 | - Option 1 is least favorable as it makes one bad header break the entire distributed trace. 130 | - Option 2 is better. It's easy, doesn't restrict future version in any way and re-started trace should be understood by new systems. So only one "connection" is lost. And the lost connection issue can be solved by storing the original header in `tracestate`. Drawbacks are also obvious. First, single old component always breaks traces. Second, it's harder to transition without customer disatisfaction of broken traces. 131 | 132 | Storing original value also has negative effects. Valid `traceparent` is 55 characters (out of 512 allowed for `tracestate`). And "bad" headers could be much longer pushing valuable `tracestate` pairs out. Also this requirement increases the chance of abuse. When a bad actor will start sending a header with the version `99` that is only understood by that actor. And the fact that every system passes thru the original value allows this actor to build a complete solution based on this header. 133 | - Option 3 with the fallback to option 2 seems to allow the easiest transition between versions by forcing a lot of restrictions on the future. Initial proposal was to try to parse individual parts like `trace-id` than `parent-id`. Assuming `parent-id` size or format may change without changing `trace-id`. However the majority sees potential for abuse here. So we suggest to force future versions to be additive to the current format. And if parsing fails at any stage - simply restart the trace. 134 | 135 | ## Restarting trace 136 | 137 | Developers sometimes feel a need to restart a trace due to security concerns, however trace context should not contain information that can compromise your application, and tracing vendors should work through the risks of trusting incoming identifiers without compromising on interoperability. 138 | 139 | If restarting a trace is unavoidable, you SHOULD clear the `tracestate` list as well. The data carried in `tracestate` carries information that often has a strong association with the `traceparent`, particularly `trace-id`. Tracing vendors will often assume that these values are associated, which is why `tracestate` SHOULD be cleared when `traceparent` is changed. 140 | 141 | There are scenarios, however, which may require different behavior. For example, if a trace was restarted when entered some secure boundaries and than restored back when it is leaving those boundaries - keeping the original `tracestate` entries will fully restore the trace back to normal. In this example, vendor-specific context will be propagated through these secure boundaries. 142 | 143 | Scenarios like the one above require careful coding and understanding of what they are trying to achieve, and should be considered an exception. Implementations should discourage this type of restarts. For example, implementations may allow this scenario only by means of restarting the trace WITH `tracestate` clean up and than re-population of `tracestate` can only be implemented as an explicit copy/pasting of `tracestate` entries one by one. 144 | 145 | ## Response headers 146 | 147 | **TL;DR;** There are many scenarios where collaboration between distributed tracing vendors require writing and reading response headers. We can see that this can have value, but don't think right now is the right time to standardize. We decided we would rather wait for individual vendors to start to collaborate over response headers and later decide which scenarios are worth standardizing. Use of `traceparent` and `tracestate` headers is not forbidden in response headers. 148 | 149 | ### Use Cases 150 | 151 | 1. Restart a trace and return new trace identification information to caller. 152 | 2. Send Tenant ID/identity of the service so caller knows where to query telemetry from. 153 | 3. Notify upstream to sample trace sending a sampling flag (+ sampling score) for delegated sampling. 154 | 4. Report back data to the caller (like server timing, method name, application type and version). E.g. for HTTP call - caller only knows the url when server knows route information. Route information may be helpful to caller to group outgoing requests. 155 | 156 | ### Open Issues 157 | 158 | - If standard defines response headers - are they required or optional? 159 | - How are they propagated to the caller of the caller? Is this done via multiple hops? 160 | - Some IoT devices may not expect relatively large response headers. 161 | 162 | ### Potential Content of a Header 163 | 164 | - `traceparent` can be used for use cases 1 and 3 (identity and deferred sampling). 165 | - `tracestate`-like header can be used for all use cases. 166 | 167 | ### Problems 168 | 169 | - Might not work in all scenarios (e.g queues). 170 | - Not sure what processing etc. would look like. 171 | -------------------------------------------------------------------------------- /implementations.md: -------------------------------------------------------------------------------- 1 | # Implementations of Trace Context 2 | 3 | This page contains an alphabetical list of projects and vendors that currently implement Trace Context. 4 | 5 | ## Azure Monitor 6 | 7 | **Website:** [Azure Monitor](https://azure.microsoft.com/services/monitor/) 8 | 9 | **Implementations:** 10 | | Implementations | Specification Level | 11 | | --------------- | :-----------------: | 12 | | [All technologies supported by Azure Monitor](https://docs.microsoft.com/azure/azure-monitor/) | [1](https://www.w3.org/TR/trace-context-1/) | 13 | 14 | ## Dynatrace 15 | 16 | **Website:** [dynatrace.com](https://www.dynatrace.com) 17 | 18 | **Implementations:** 19 | | Implementations | Specification Level | 20 | | --------------- | :-----------------: | 21 | | [All technologies supported by Dynatrace OneAgent](https://www.dynatrace.com/news/blog/distributed-tracing-with-w3c-trace-context-for-improved-end-to-end-visibility-eap/) | [1](https://www.w3.org/TR/trace-context-1/) | 22 | 23 | ## Elastic 24 | 25 | **Website:** [https://elastic.co/products/apm](https://elastic.co/products/apm) 26 | 27 | **Implementations:** 28 | | Implementations | Specification Level | 29 | | --------------- | :-----------------: | 30 | | [.NET](https://github.com/elastic/apm-agent-dotnet/blob/700754909b1ac522796294b99adcc98063efcf42/src/Elastic.Apm/DistributedTracing/TraceParent.cs) | [1](https://www.w3.org/TR/trace-context-1/) | 31 | | [Go](https://github.com/elastic/apm-agent-go/blob/0e868bf43005f3f5b3786101960137d7c8760361/module/apmhttp/traceheaders.go) | [1](https://www.w3.org/TR/trace-context-1/) | 32 | | [Java](https://github.com/elastic/apm-agent-java/blob/e4cdde0b860ff37ea57e0ca083c62b319c0ee940/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java) | [1](https://www.w3.org/TR/trace-context-1/) | 33 | | [Node.js](https://github.com/elastic/node-traceparent) | [1](https://www.w3.org/TR/trace-context-1/) | 34 | | [Python](https://github.com/elastic/apm-agent-python/blob/50dce143ae15f6c592a70cb858a8c4721dd80ef5/elasticapm/utils/disttracing.py) | [1](https://www.w3.org/TR/trace-context-1/) | 35 | | [Ruby](https://github.com/elastic/apm-agent-ruby/blob/b68f1f12ae48a5c6e757241c65de97a98488ee6a/lib/elastic_apm/trace_context.rb) | [1](https://www.w3.org/TR/trace-context-1/) | 36 | | [JavaScript - Real User Monitoring](https://github.com/elastic/apm-agent-rum-js) | [1](https://www.w3.org/TR/trace-context-1/) | 37 | 38 | ## IBM Observability by Instana 39 | 40 | **Website:** [instana.com](https://www.instana.com) 41 | 42 | **Implementations:** 43 | | Implementations | Specification Level | 44 | | --------------- | :-----------------: | 45 | | [Apache Httpd Tracing](https://www.ibm.com/docs/en/instana-observability/current?topic=technologies-monitoring-apache-httpd) | [1](https://www.w3.org/TR/trace-context-1/) | 46 | | [.NET Core](https://www.nuget.org/packages/Instana.Tracing.Core) | [1](https://www.w3.org/TR/trace-context-1/) | 47 | | [Go](https://github.com/instana/go-sensor) | [1](https://www.w3.org/TR/trace-context-1/) | 48 | | [Haskell](https://hackage.haskell.org/package/instana-haskell-trace-sdk) | [2](https://www.w3.org/TR/trace-context-2/) | 49 | | [Java/Clojure/Kotlin/Scala](https://www.ibm.com/docs/en/SSE1JP5_current/src/pages/ecosystem/jvm/index.html#instana-autotrace) | [1](https://www.w3.org/TR/trace-context-1/) | 50 | | [Node.js](https://www.npmjs.com/package/@instana/collector) | [2](https://www.w3.org/TR/trace-context-2/) | 51 | | [PHP](https://www.ibm.com/docs/en/instana-observability/current?topic=technologies-monitoring-php) | [1](https://www.w3.org/TR/trace-context-1/) | 52 | | [Python](https://pypi.org/project/instana/) | [1](https://www.w3.org/TR/trace-context-1/) | 53 | | [Ruby](https://rubygems.org/gems/instana/) | [1](https://www.w3.org/TR/trace-context-1/) | 54 | 55 | ## Jaeger 56 | 57 | **Website:** [jaegertracing.io](https://www.jaegertracing.io) 58 | 59 | **Implementations:** 60 | | Implementations | Specification Level | 61 | | --------------- | :-----------------: | 62 | | [Java](https://github.com/jaegertracing/jaeger-client-java/blob/b50aa159e3949461509d451fa1ded91887b680ad/jaeger-core/src/main/java/io/jaegertracing/internal/propagation/TraceContextCodec.java) | [1](https://www.w3.org/TR/trace-context-1/) | 63 | 64 | ## Kamon 65 | 66 | **Website:** [Kamon.io](https://kamon.io/) 67 | 68 | **Implementations:** 69 | | Implementations | Specification Level | 70 | | --------------- | :-----------------: | 71 | | [Java](https://github.com/kamon-io/Kamon/blob/4d5ec29df5/core/kamon-core/src/main/scala/kamon/trace/SpanPropagation.scala#L72) | [1](https://www.w3.org/TR/trace-context-1/) | 72 | 73 | ## LightStep 74 | 75 | **Website:** [lightstep.com](https://lightstep.com) 76 | 77 | **Implementations:** 78 | | Implementations | Specification Level | 79 | | --------------- | :-----------------: | 80 | | [Go](https://github.com/lightstep/tracecontext.go) | [1](https://www.w3.org/TR/trace-context-1/) | 81 | 82 | ## New Relic 83 | 84 | **Website:** [newrelic.com](https://newrelic.com/) 85 | 86 | **Implementations:** 87 | | Implementations | Specification Level | 88 | | --------------- | :-----------------: | 89 | | All agents will support W3C Trace Context, see the list of compatible agents [here](https://docs.newrelic.com/docs/understand-dependencies/distributed-tracing/enable-configure/enable-distributed-tracing#compatibility-requirements) | [1](https://www.w3.org/TR/trace-context-1/) | 90 | 91 | ## OpenCensus 92 | 93 | **Website:** [opencensus.io](https://opencensus.io) 94 | 95 | **Implementations:** 96 | | Implementations | Specification Level | 97 | | --------------- | :-----------------: | 98 | | [C#](https://github.com/census-instrumentation/opencensus-csharp/blob/4a8ddf6727eafda97a06c7c30d8a4fc2ec8b8e2f/src/OpenCensus/Trace/Propagation/TraceContextFormat.cs) | [1](https://www.w3.org/TR/trace-context-1/) | 99 | | [Erlang](https://github.com/census-instrumentation/opencensus-erlang/blob/b3ab781b060b15a3cacbf43717c3aeb0c90c4a08/src/oc_propagation_http_tracecontext.erl) | [1](https://www.w3.org/TR/trace-context-1/) | 100 | | [Go](https://github.com/census-instrumentation/opencensus-go/blob/ae11cd04b7789fa938bb4f0e696fd6bd76463fa4/plugin/ochttp/propagation/tracecontext/propagation.go) | [1](https://www.w3.org/TR/trace-context-1/) | 101 | | [Java](https://github.com/census-instrumentation/opencensus-java/blob/e5e9d9224a1c9c5ee981981cf29e86662aef08c6/impl_core/src/main/java/io/opencensus/implcore/trace/propagation/TraceContextFormat.java) | [1](https://www.w3.org/TR/trace-context-1/) | 102 | | [Node.js](https://github.com/census-instrumentation/opencensus-node/blob/fa97a9b6f19b97e1038ffa9e1be4b407f3844df2/packages/opencensus-propagation-tracecontext/src/tracecontext-format.ts) | [1](https://www.w3.org/TR/trace-context-1/) | 103 | | [Python](https://github.com/census-instrumentation/opencensus-python/blob/2aef803e4a786fe0ffb14b168a8458283ccd72a0/opencensus/trace/propagation/trace_context_http_header_format.py) | [1](https://www.w3.org/TR/trace-context-1/) | 104 | | [Ruby](https://github.com/census-instrumentation/opencensus-ruby/blob/8cb9771b218e440e825c99981ea405d40f735926/lib/opencensus/trace/formatters/trace_context.rb) | [1](https://www.w3.org/TR/trace-context-1/) | 105 | 106 | ## OpenTelemetry 107 | 108 | **Website:** [opentelemetry.io](https://opentelemetry.io) 109 | 110 | **Implementations:** 111 | | Implementations | Specification Level | 112 | | --------------- | :-----------------: | 113 | | [.NET](https://github.com/open-telemetry/opentelemetry-dotnet/blob/dcaea5bd456ba9c3515a578fb9645c5a9ae4af0d/src/OpenTelemetry.Api/Context/Propagation/TraceContextFormat.cs#L29) | [1](https://www.w3.org/TR/trace-context-1/) | 114 | | [Go](https://github.com/open-telemetry/opentelemetry-go/blob/3362421c9b41feb586ab003857894d470be57169/plugin/httptrace/httptrace.go) | [1](https://www.w3.org/TR/trace-context-1/) | 115 | | [Java](https://github.com/open-telemetry/opentelemetry-java/blob/63109827ea3ceba7aa099d1d0a612741a887dbac/api/src/main/java/io/opentelemetry/trace/propagation/HttpTraceContext.java) | [1](https://www.w3.org/TR/trace-context-1/) | 116 | | [JavaScript](https://github.com/open-telemetry/opentelemetry-js/blob/a49e7abdab3e313ad2b50a9445a885b3fd0d4783/packages/opentelemetry-core/src/context/propagation/HttpTraceContext.ts) | [1](https://www.w3.org/TR/trace-context-1/) | 117 | | [Python](https://github.com/open-telemetry/opentelemetry-python/blob/dbb3be802bae8e4e5c36748869dbc789e50de217/opentelemetry-api/src/opentelemetry/trace/__init__.py) | [1](https://www.w3.org/TR/trace-context-1/) | 118 | | [Ruby](https://github.com/open-telemetry/opentelemetry-ruby/blob/741ca61a934b05ecbaedffa56a830dc1821ca9a1/api/lib/opentelemetry/distributed_context/propagation/trace_parent.rb) | [1](https://www.w3.org/TR/trace-context-1/) | 119 | 120 | ## tctx 121 | 122 | **Website:** [github.com/maraisr/tctx](https://github.com/maraisr/tctx) 123 | 124 | **Implementations:** 125 | | Implementations | Specification Level | 126 | | --------------- | :-----------------: | 127 | | [JavaScript](https://github.com/maraisr/tctx) | [2](https://www.w3.org/TR/trace-context-2/) | 128 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Trace Context 6 | 7 | 90 | 91 | 92 | 93 |
94 |
95 | 96 |
97 | 98 |
99 | 100 |
101 |
102 | 103 |
104 | 105 |
106 | 107 |
108 |
109 | 110 |
111 |
112 | 113 |
114 | 115 |
116 |

Glossary

117 |
118 |
Distributed trace
119 |
120 | A distributed trace is a set of events, triggered as a result 121 | of a single logical operation, consolidated across various 122 | components of an application. A distributed trace contains 123 | events that cross process, network and security boundaries. 124 | A distributed trace may be initiated when someone presses a 125 | button to start an action on a website - in this example, the 126 | trace will represent calls made between the downstream services 127 | that handled the chain of requests initiated by this button 128 | being pressed. 129 |
130 |
131 |
132 |
Opaque value
133 |
134 | An opaque value refers to a value that can only be understood 135 | or processed in any way by the distributed trace participant that generated 136 | this value. Any other participant must treat it as a blob of bytes. 137 |
138 |
139 |
140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /spec/01-abstract.md: -------------------------------------------------------------------------------- 1 | This specification defines standard HTTP headers and a value format to propagate context information that enables distributed tracing scenarios. The specification standardizes how context information is sent and modified between services. Context information uniquely identifies individual requests in a distributed system and also defines a means to add and propagate provider-specific context information. 2 | -------------------------------------------------------------------------------- /spec/02-sotd.md: -------------------------------------------------------------------------------- 1 | This specification is in early stage. It was widely viewed and discussed. 2 | -------------------------------------------------------------------------------- /spec/10-overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | ## Problem Statement 4 | 5 | Distributed tracing is a methodology implemented by tracing tools to follow, analyze and debug a transaction across multiple software components. Typically, a distributed trace traverses more than one component which requires it to be uniquely identifiable across all participating systems. Trace context propagation passes along this unique identification. Today, trace context propagation is implemented individually by each tracing vendor. In multi-vendor environments, this causes interoperability problems, like: 6 | 7 | - Traces that are collected by different tracing vendors cannot be correlated as there is no shared unique identifier. 8 | - Traces that cross boundaries between different tracing vendors can not be propagated as there is no uniformly agreed set of identification that is forwarded. 9 | - Vendor specific metadata might be dropped by intermediaries. 10 | - Cloud platform vendors, intermediaries and service providers, cannot guarantee to support trace context propagation as there is no standard to follow. 11 | 12 | In the past, these problems did not have a significant impact as most applications were monitored by a single tracing vendor and stayed within the boundaries of a single platform provider. Today, an increasing number of applications are highly distributed and leverage multiple middleware services and cloud platforms. 13 | 14 | This transformation of modern applications calls for a distributed tracing context propagation standard. 15 | 16 | ## Solution 17 | 18 | The trace context specification defines a universally agreed-upon format for the exchange of trace context propagation data - referred to as *trace context*. Trace context solves the problems described above by 19 | 20 | - providing a unique identifier for individual traces and requests, allowing trace data of multiple providers to be linked together. 21 | - providing an agreed-upon mechanism to forward vendor-specific trace data and avoid broken traces when multiple tracing tools participate in a single transaction. 22 | - providing an industry standard that intermediaries, platforms, and hardware providers can support. 23 | 24 | A unified approach for propagating trace data improves visibility into the behavior of distributed applications, facilitating problem and performance analysis. The interoperability provided by trace context is a prerequisite to manage modern micro-service based applications. 25 | 26 | The current version of the Trace Context specification is targeted for implementations by applications and services, including web applications running within a browser. Web Browsers or User Agents are not currently in scope as a target implementation. 27 | 28 | ## Design Overview 29 | 30 | Trace context is split into two individual propagation fields supporting interoperability and vendor-specific extensibility: 31 | 32 | - `traceparent` describes the position of the incoming request in its trace graph in a portable, fixed-length format. Its design focuses on fast parsing. Every tracing tool MUST properly set `traceparent` even when it only relies on vendor-specific information in `tracestate` 33 | - `tracestate` extends `traceparent` with vendor-specific data represented by a set of name/value pairs. Storing information in `tracestate` is optional. 34 | 35 | Tracing tools can provide two levels of compliant behavior interacting with trace context: 36 | 37 | - At a minimum they MUST propagate the `traceparent` and `tracestate` headers and guarantee traces are not broken. This behavior is also referred to as forwarding a trace. 38 | - In addition they MAY also choose to participate in a trace by modifying the `traceparent` header and relevant parts of the `tracestate` header containing their proprietary information. This is also referred to as participating in a trace. 39 | 40 | A tracing tool can choose to change this behavior for each individual request to a component it is monitoring. 41 | -------------------------------------------------------------------------------- /spec/20-http_request_header_format.md: -------------------------------------------------------------------------------- 1 | # Trace Context HTTP Request Headers Format 2 | 3 | This section describes the binding of the distributed trace context to `traceparent` and `tracestate` HTTP headers. 4 | 5 | ## Relationship Between the Headers 6 | 7 | The `traceparent` request header represents the incoming request in a tracing system in a common format, understood by all vendors. Here’s an example of a `traceparent` header. 8 | 9 | ``` http 10 | traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01 11 | ``` 12 | 13 | The `tracestate` request header includes the parent in a potentially vendor-specific format: 14 | 15 | ``` http 16 | tracestate: congo=t61rcWkgMzE 17 | ``` 18 | 19 | For example, say a client and server in a system use different tracing vendors: Congo and Rojo. A client traced in the Congo system adds the following headers to an outbound HTTP request. 20 | 21 | ``` http 22 | traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01 23 | tracestate: congo=t61rcWkgMzE 24 | ``` 25 | 26 | **Note**: In this case, the `tracestate` value `t61rcWkgMzE` is the result of Base64 encoding the parent ID (`b7ad6b7169203331`), though such manipulations are not required. 27 | 28 | The receiving server, traced in the Rojo tracing system, carries over the `tracestate` it received and adds a new entry to the left. 29 | 30 | ``` http 31 | traceparent: 00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01 32 | tracestate: rojo=00f067aa0ba902b7,congo=t61rcWkgMzE 33 | ``` 34 | 35 | You'll notice that the Rojo system reuses the value of its `traceparent` for its entry in `tracestate`. This means it is a generic tracing system (no proprietary information is being passed). Otherwise, `tracestate` entries are [=opaque=] and can be vendor-specific. 36 | 37 | If the next receiving server uses Congo, it carries over the `tracestate` from Rojo and adds a new entry for the parent to the left of the previous entry. 38 | 39 | ``` http 40 | traceparent: 00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01 41 | tracestate: congo=ucfJifl5GOE,rojo=00f067aa0ba902b7 42 | ``` 43 | 44 | **Note:** `ucfJifl5GOE` is the Base64 encoded parent ID `b9c7c989f97918e1`. 45 | 46 | Notice when Congo wrote its `traceparent` entry, it is not encoded, which helps in consistency for those doing correlation. However, the value of its entry `tracestate` is encoded and different from `traceparent`. This is ok. 47 | 48 | Finally, you'll see `tracestate` retains an entry for Rojo exactly as it was, except pushed to the right. The left-most position lets the next server know which tracing system corresponds with `traceparent`. In this case, since Congo wrote `traceparent`, its `tracestate` entry should be left-most. 49 | 50 | 51 | ## Traceparent Header 52 | 53 | The `traceparent` HTTP header field identifies the incoming request in a tracing system. It has four fields: 54 | 55 | * `version` 56 | * `trace-id` 57 | * `parent-id` 58 | * `trace-flags` 59 | 60 | 61 | ### Header Name 62 | 63 | Header name: `traceparent` 64 | 65 | The header name is [ASCII case-insensitive](https://infra.spec.whatwg.org/#ascii-case-insensitive). That is, `TRACEPARENT`, `TraceParent`, and `traceparent` are considered the same header. The header name is a single word; it does not contain any delimiters such as a hyphen. 66 | 67 | In order to increase interoperability across multiple protocols and encourage successful integration, tracing systems SHOULD encode the header name as [ASCII lowercase](https://infra.spec.whatwg.org/#ascii-lowercase). 68 | 69 | ### traceparent Header Field Values 70 | 71 | This section uses the Augmented Backus-Naur Form (ABNF) notation of [[!RFC5234]], including the DIGIT rule from that document. The `DIGIT` rule defines a single number character `0`-`9`. 72 | 73 | ``` abnf 74 | HEXDIGLC = DIGIT / "a" / "b" / "c" / "d" / "e" / "f" ; lowercase hex character 75 | value = version "-" version-format 76 | ``` 77 | 78 | The dash (`-`) character is used as a delimiter between fields. 79 | 80 | #### version 81 | 82 | ``` abnf 83 | version = 2HEXDIGLC ; this document assumes version 00. Version ff is forbidden 84 | ``` 85 | 86 | Version (`version`) is an 8-bit unsigned integer value, serialized as an ASCII string with two characters. Version 255 (`"ff"`) is invalid. This document specifies version 0 (`"00"`) of the `traceparent` header. 87 | 88 | #### version-format 89 | 90 | The following `version-format` definition is used for version `00`. 91 | 92 | ``` abnf 93 | version-format = trace-id "-" parent-id "-" trace-flags 94 | trace-id = 32HEXDIGLC ; 16 bytes array identifier. All zeroes forbidden 95 | parent-id = 16HEXDIGLC ; 8 bytes array identifier. All zeroes forbidden 96 | trace-flags = 2HEXDIGLC ; 8 bit flags. 97 | ``` 98 | 99 | #### trace-id 100 | 101 | This is the ID of the whole trace forest and is used to uniquely identify a distributed trace through a system. 102 | It is represented as a 16-byte array, for example, `4bf92f3577b34da6a3ce929d0e0e4736`. 103 | All bytes as zero (`00000000000000000000000000000000`) is considered an invalid value. 104 | 105 | The value of `trace-id` SHOULD be globally unique. 106 | One recommended method to ensure global uniqueness, as well as to address some privacy and security considerations, to a satisfactory degree of certainty is to randomly (or pseudo-randomly) generate the `trace-id`. 107 | Implementers SHOULD use a `trace-id` generation method which randomly (or pseudo-randomly) generates at least the right-most 7 bytes of the ID. 108 | If the right-most 7 bytes are randomly (or pseudo-randomly) generated, the corresponding [random trace id flag](#random-trace-id-flag) SHOULD be set. 109 | For more details, see [considerations for trace-id field generation](#considerations-for-trace-id-field-generation). 110 | 111 | If the `trace-id` value is invalid (for example if it contains non-allowed characters or all zeros), vendors MUST ignore the entire header. 112 | 113 | #### parent-id 114 | 115 | This is the ID of this request as known by the caller (in some tracing systems, this is known as the `span-id`, where a `span` is the execution of a client request). It is represented as an 8-byte array, for example, `00f067aa0ba902b7`. All bytes as zero (`0000000000000000`) is considered an invalid value. 116 | 117 | Vendors MUST ignore the `traceparent` when the `parent-id` is invalid (for example, if it contains non-lowercase hex characters). 118 | 119 | #### trace-flags 120 | 121 | This is an 8-bit field that controls tracing flags such as sampling, trace level, etc. These flags are recommendations given by the caller rather than strict rules to follow for three reasons: 122 | 123 | 1. An untrusted caller may be able to abuse a tracing system by setting these flags maliciously. 124 | 2. A caller may have a bug which causes the tracing system to have a problem. 125 | 3. Different load between caller service and callee service might force callee to downsample. 126 | 127 | You can find more in the section [Security considerations](#security-considerations) of this specification. 128 | 129 | Like other fields, `trace-flags` is hex-encoded. For example, all `8` flags set would be `ff` and no flags set would be `00`. 130 | 131 | As this is a bit field, the flags cannot be interpreted by a simple equality comparison. 132 | For example, both `01` (`00000001`) and `03` (`00000011`) represent that the trace has been sampled because the sampled flag (`00000001`) is set, and `03` and `02` (`00000010`) both represent that at least the right-most 7 bytes of the `trace-id` are randomly (or pseudo-randomly) generated because the random bit (`00000010`) is set. 133 | A common mistake when interpreting bit-fields is using a comparison of the whole number rather than interpreting a single bit. 134 | 135 | Here is an example of properly handling trace flags: 136 | 137 | ``` java 138 | static final byte FLAG_SAMPLED = 1; // 00000001 139 | static final byte FLAG_RANDOM = 2; // 00000010 140 | ... 141 | boolean sampled = (traceFlags & FLAG_SAMPLED) == FLAG_SAMPLED; 142 | boolean random = (traceFlags & FLAG_RANDOM) == FLAG_RANDOM; 143 | ``` 144 | 145 | ##### Sampled flag 146 | 147 | When set, the least significant bit (right-most), denotes that the caller may have recorded trace data. When unset, the caller did not record trace data out-of-band. 148 | 149 | There are a number of recording scenarios that may break distributed tracing: 150 | 151 | - Only recording a subset of requests results in broken traces. 152 | - Recording information about all incoming and outgoing requests becomes prohibitively expensive, at load. 153 | - Making random or component-specific data collection decisions leads to fragmented data in all traces. 154 | 155 | Because of these issues, tracing vendors make their own recording decisions, and there is no consensus on what is the best algorithm for this job. 156 | 157 | Various techniques include: 158 | 159 | - Probability sampling (sample 1 out of 100 distributed traces by flipping a coin) 160 | - Delayed decision (make collection decision based on duration or a result of a request) 161 | - Deferred sampling (let the callee decide whether information about this request needs to be collected) 162 | 163 | How these techniques are implemented can be tracing vendor-specific or application-defined. 164 | 165 | The `tracestate` field is designed to handle the variety of techniques for making recording decisions (or other specific information) specific for a given vendor. The `sampled` flag provides better interoperability between vendors. It allows vendors to communicate recording decisions and enable a better experience for the customer. 166 | 167 | For example, when a SaaS service participates in a distributed trace, this service has no knowledge of the tracing vendor used by its caller. This service may produce records of incoming requests for monitoring or troubleshooting purposes. The `sampled` flag can be used to ensure that information about requests that were marked for recording by the caller will also be recorded by SaaS service downstream so that the caller can troubleshoot the behavior of every recorded request. 168 | 169 | The `sampled` flag has no restriction on its mutations except that it can only be mutated when [parent-id is updated](#parent-id). 170 | 171 | The following are a set of suggestions that vendors SHOULD use to increase vendor interoperability. 172 | 173 | - If a component made definitive recording decision - this decision SHOULD be reflected in the `sampled` flag. 174 | - If a component needs to make a recording decision - it SHOULD respect the `sampled` flag value. 175 | [Security considerations](#security-considerations) SHOULD be applied to protect from abusive or malicious use of this flag. 176 | - If a component deferred or delayed the decision and only a subset of telemetry will be recorded, the `sampled` flag should be propagated unchanged. It should be set to `0` as the default option when the trace is initiated by this component. 177 | 178 | There are two additional options that vendors MAY follow: 179 | 180 | - A component that makes a deferred or delayed recording decision may communicate the priority of a recording by setting `sampled` flag to `1` for a subset of requests. 181 | - A component may also fall back to probability sampling and set the `sampled` flag to `1` for the subset of requests. 182 | 183 | ##### Random Trace ID Flag 184 | 185 | The second least significant bit of the trace-flags field denotes the `random-trace-id` flag. 186 | 187 | When starting or restarting a trace (that is, when the participant generates a new `trace-id`), the following rules apply: 188 | * If that flag is set, at least the right-most 7 bytes of the `trace-id` MUST be selected randomly (or pseudo-randomly) with uniform distribution over the interval [0..2^56-1]. 189 | * If the flag is not set, the `trace-id` MAY still be randomly (or pseudo-randomly) generated. 190 | * When unset, the `trace-id` MAY be generated in any way that satisfies the requirements of the [trace ID format](#trace-id). 191 | * When at least the right-most 7 bytes of the `trace-id` are randomly (or pseudo-randomly) generated, the `random-trace-id` flag SHOULD be set to `1`. 192 | 193 | When continuing a trace (that is, the incoming HTTP request had the `traceparent` header and the participant uses the same `trace-id` in the `traceparent` header on outgoing requests), the following rules apply: 194 | * If the flag is set in the incoming `traceparent` header, it MUST also be set in all outgoing `traceparent` headers which use the same `trace-id`. 195 | * If the flag is unset in the incoming `traceparent` header, it MUST also be unset in any outgoing `traceparent` headers which use the same `trace-id`. 196 | 197 | This allows downstream consumers to implement features such as trace sampling or database sharding based on these bytes. 198 | For additional information, see [considerations for trace-id field generation](#considerations-for-trace-id-field-generation). 199 | 200 | ##### Other Flags 201 | 202 | The behavior of other flags, such as (`00000100`) is not defined and is reserved for future use. Vendors MUST set those to zero. 203 | 204 | ### Examples of HTTP traceparent Headers 205 | 206 | *Valid traceparent when caller sampled this request:* 207 | 208 | ``` 209 | Value = 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 210 | base16(version) = 00 211 | base16(trace-id) = 4bf92f3577b34da6a3ce929d0e0e4736 212 | base16(parent-id) = 00f067aa0ba902b7 213 | base16(trace-flags) = 01 // sampled 214 | ``` 215 | 216 | *Valid traceparent when caller didn’t sample this request:* 217 | 218 | ``` 219 | Value = 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00 220 | base16(version) = 00 221 | base16(trace-id) = 4bf92f3577b34da6a3ce929d0e0e4736 222 | base16(parent-id) = 00f067aa0ba902b7 223 | base16(trace-flags) = 00 // not sampled 224 | ``` 225 | 226 | ### Versioning of traceparent 227 | 228 | This specification is opinionated about future versions of trace context. The current version of this specification assumes that future versions of the `traceparent` header will be additive to the current one. 229 | 230 | Vendors MUST follow these rules when parsing headers with an unexpected format: 231 | 232 | - Pass-through services should not analyze the version. They should expect that headers may have larger size limits in the future and only disallow prohibitively large headers. 233 | - When the version prefix cannot be parsed (it's not 2 hex characters followed by a dash (`-`)), the implementation should restart the trace. 234 | - If a higher version is detected, the implementation SHOULD try to parse it by trying the following: 235 | - If the size of the header is shorter than 55 characters, the vendor should not parse the header and should restart the trace. 236 | - Parse `trace-id` (from the first dash through the next 32 characters). Vendors MUST check that the 32 characters are hex, and that they are followed by a dash (`-`). 237 | - Parse `parent-id` (from the second dash at the 35th position through the next 16 characters). Vendors MUST check that the 16 characters are hex and followed by a dash. 238 | - Parse the `sampled` bit of `flags` (2 characters from the third dash). Vendors MUST check that the 2 characters are either at the end of the string or followed by a dash. 239 | 240 | If all three values were parsed successfully, the vendor should use them. 241 | 242 | 243 | Vendors MUST NOT parse or assume anything about unknown fields for this version. Vendors MUST use these fields to construct the new `traceparent` field according to the highest version of the specification known to the implementation (in this specification it is `00`). 244 | 245 | ## Tracestate Header 246 | 247 | The main purpose of the `tracestate` HTTP header is to provide additional vendor-specific trace identification information across different distributed tracing systems and is a companion header for the `traceparent` field. It also conveys information about the request’s position in multiple distributed tracing graphs. 248 | 249 | If the vendor failed to parse `traceparent`, it MUST NOT attempt to parse `tracestate`. Note that the opposite is not true: failure to parse `tracestate` MUST NOT affect the parsing of `traceparent`. 250 | 251 | The `tracestate` HTTP header MUST NOT be used for any properties that are not defined by a tracing system. [[BAGGAGE]] MAY be used for defining and propagating such application level properties. 252 | 253 | ### Header Name 254 | 255 | Header name: `tracestate` 256 | 257 | The header name is [ASCII case-insensitive](https://infra.spec.whatwg.org/#ascii-case-insensitive). That is, `TRACESTATE`, `TraceState`, and `tracestate` are considered the same header. The header name is a single word, it does not contain any delimiters such as a hyphen. 258 | 259 | In order to increase interoperability across multiple protocols and encourage successful integration, tracing systems SHOULD encode the header name as [ASCII lowercase](https://infra.spec.whatwg.org/#ascii-lowercase). 260 | 261 | ### tracestate Header Field Values 262 | 263 | The `tracestate` field may contain any [=opaque=] value in any of the keys. Tracestate MAY be sent or received as multiple header fields. Multiple tracestate header fields MUST be handled as specified by RFC9110 Section 5.3 Field Order. The `tracestate` header SHOULD be sent as a single field when possible, but MAY be split into multiple header fields. When sending `tracestate` as multiple header fields, it MUST be split according to RFC9110. When receiving multiple `tracestate` header fields, they MUST be combined into a single header according to RFC9110. 264 | 265 | This section uses the Augmented Backus-Naur Form (ABNF) notation of [[!RFC5234]], including the DIGIT rule in appendix B.1 for RFC5234. It also includes the `OWS` rule from RFC9110 section 5.6.3. 266 | 267 | The `DIGIT` rule defines numbers `0`-`9`. 268 | 269 | The `OWS` rule defines an optional whitespace character. To improve readability, it is used where zero or more whitespace characters might appear. 270 | 271 | The caller SHOULD generate the optional whitespace as a single space; otherwise, a caller SHOULD NOT generate optional whitespace. See details in the corresponding RFC. 272 | 273 | The `tracestate` field value is a `list` of `list-members` separated by commas (`,`). A `list-member` is a key/value pair separated by an equals sign (`=`). Spaces and horizontal tabs surrounding `list-member`s are ignored. There can be a maximum of 32 `list-member`s in a `list`. If adding an entry would cause the `tracestate` list to contain more than 32 `list-members` the right-most `list-member` should be removed from the list. 274 | 275 | Empty and whitespace-only list members are allowed. Vendors MUST accept empty `tracestate` headers but SHOULD avoid sending them. Empty list members are allowed in `tracestate` because it is difficult for a vendor to recognize the empty value when multiple `tracestate` headers are sent. Whitespace characters are allowed for a similar reason, as some vendors automatically inject whitespace after a comma separator, even in the case of an empty header. 276 | 277 | ##### list 278 | 279 | A simple example of a `list` with two `list-member`s might look like: 280 | `vendorname1=opaqueValue1,vendorname2=opaqueValue2`. 281 | 282 | ``` abnf 283 | list = list-member 0*31( OWS "," OWS list-member ) 284 | list-member = (key "=" value) / OWS 285 | ``` 286 | 287 | Identifiers for a `list` are short (up to 256 characters) textual identifiers. 288 | 289 | ##### list-members 290 | 291 | A `list-member` contains a key/value pair. 292 | 293 | ###### Key 294 | 295 | The key is an identifier that describes the vendor. 296 | 297 | ``` abnf 298 | key = ( lcalpha / DIGIT ) 0*255 ( keychar ) 299 | keychar = lcalpha / DIGIT / "_" / "-"/ "*" / "/" / "@" 300 | lcalpha = %x61-7A ; a-z 301 | ``` 302 | 303 | A `key` MUST begin with a lowercase letter or a digit and contain up to 256 characters including lowercase letters (`a`-`z`), digits (`0`-`9`), underscores (`_`), dashes (`-`), asterisks (`*`), forward slashes (`/`), and at signs (`@`). 304 | 305 | ###### Value 306 | 307 | The value is an opaque string containing up to 256 printable ASCII [[!RFC0020]] characters (i.e., the range 0x20 to 0x7E) except comma (,) and (=). The string must end with a character which is not a space (0x20). Note that this also excludes tabs, newlines, carriage returns, etc. All leading spaces MUST be preserved as part of the value. All trailing spaces are considered to be optional whitespace characters not part of the value. Optional trailing whitespace MAY be excluded when propagating the header. 308 | 309 | ``` abnf 310 | value = 0*255(chr) nblk-chr 311 | nblk-chr = %x21-2B / %x2D-3C / %x3E-7E 312 | chr = %x20 / nblk-chr 313 | ``` 314 | 315 | ### Combined Header Value 316 | 317 | The `tracestate` value is the concatenation of trace graph key/value pairs. 318 | 319 | Example: `vendorname1=opaqueValue1,vendorname2=opaqueValue2` 320 | 321 | Tracing tools are not supposed to add the same header multiple times. For example, if a vendor name is Congo and a trace started in their system and then went through a system named Rojo and later returned to Congo, the `tracestate` value would not be: 322 | 323 | `congo=congosFirstPosition,rojo=rojosFirstPosition,congo=congosSecondPosition` 324 | 325 | Instead, the entry would be rewritten to only include the most recent position: 326 | `congo=congosSecondPosition,rojo=rojosFirstPosition` 327 | 328 | See [Mutating the tracestate Field](#mutating-the-tracestate-field) for details. 329 | 330 | #### tracestate Limits: 331 | 332 | Vendors SHOULD propagate at least 512 characters of a combined header. This length includes commas required to separate list items and optional white space (`OWS`) characters. 333 | 334 | There are systems where propagating of 512 characters of `tracestate` may be expensive. In this case, the maximum size of the propagated `tracestate` header SHOULD be documented and explained. The cost of propagating `tracestate` SHOULD be weighted against the value of monitoring scenarios enabled for the end users. 335 | 336 | In a situation where `tracestate` is truncated due to the total size of the header value, the vendor MUST truncate whole entries. Entries larger than `128` characters long SHOULD be removed first. Then entries SHOULD be removed starting from the end of `tracestate`. Other truncation strategies like safe list entries, blocked list entries, or size-based truncation SHOULD NOT be used. 337 | 338 | ### Examples of tracestate HTTP Headers 339 | 340 | Single tracing system (generic format): 341 | 342 | ``` http 343 | tracestate: rojo=00f067aa0ba902b7 344 | ``` 345 | 346 | Multiple tracing systems (with different formatting): 347 | 348 | ``` http 349 | tracestate: rojo=00f067aa0ba902b7,congo=t61rcWkgMzE 350 | ``` 351 | 352 | ### Versioning of tracestate 353 | 354 | The version of `tracestate` is defined by the version prefix of `traceparent` header. Vendors need to attempt to parse `tracestate` if a higher version is detected, to the best of its ability. It is the vendor’s decision whether to use partially-parsed `tracestate` key/value pairs or not. 355 | 356 | ## Mutating the traceparent Field 357 | 358 | A vendor receiving a request without a `traceparent` header SHOULD generate `traceparent` headers for outbound requests, effectively starting a new trace. A possible reason for not doing this could be a performance sensitive scenario when the vendor decides to not sample a request. Note that for most scenarios, vendors are expected to generate the header even when not sampling, to propagate the sampling decision downstream. 359 | 360 | A vendor receiving a `traceparent` request header MUST send it to outgoing requests. It MAY mutate the value of this header before passing it to outgoing requests. 361 | 362 | If the value of the `traceparent` field wasn't changed before propagation, `tracestate` MUST NOT be modified as well. Unmodified header propagation is typically implemented in pass-through services like proxies. This behavior may also be implemented in a service which currently does not collect distributed tracing information. 363 | 364 | Following is the list of allowed mutations: 365 | 366 | - **Update `parent-id`**: The value of [the parent-id field](#parent-id) can be set to the new value representing the ID of the current operation. This is the most typical mutation and should be considered a default. 367 | - **Update `sampled`**: The value of [the sampled field](#trace-flags) reflects the caller's recording behavior: either trace data was dropped or may have been recorded out-of-band. This can be indicated by toggling the flag in both directions. This mutation gives the downstream vendor information about the likelihood that its parent's information was recorded. The `parent-id` field MUST be set to a new value with the `sampled` flag update. 368 | - **Restart trace**: All properties (`trace-id`, `parent-id`, `trace-flags`) are regenerated. This mutation is used in services that are defined as a front gate into secure networks and eliminates a potential denial-of-service attack surface. Vendors SHOULD clean up `tracestate` collection on `traceparent` restart. There are rare cases when the original `tracestate` entries must be preserved after a restart. This typically happens when the `trace-id` is reverted back at some point of the trace flow, for instance, when it leaves the secure network. However, it SHOULD be an explicit decision, and not the default behavior. 369 | - **Downgrade the version**: This version of the specification (`00`) [defines the behavior](#version) for a vendor that receives a `traceparent` header of a higher version. In this case, the first mutation is to downgrade the version of the header. Other mutations are allowed in combination with this one. 370 | 371 | Vendors MUST NOT make any other mutations to the `traceparent` header. 372 | 373 | ## Mutating the tracestate Field 374 | 375 | Vendors receiving a `tracestate` request header MUST send it to outgoing requests. It MAY mutate the value of this header before passing to outgoing requests. When mutating `tracestate`, the order of unmodified key/value pairs MUST be preserved. Modified keys MUST be moved to the beginning (left) of the list. 376 | 377 | Following are allowed mutations: 378 | 379 | - **Add a new key/value pair**. The new key/value pair SHOULD be added to the beginning of the list. Adding a key/value pair MUST NOT result in the same key being present multiple times. 380 | - **Update an existing value**. The value for any given key can be updated. Modified keys SHOULD be moved to the beginning (left) of the list. 381 | - **Delete a key/value pair**. Any key/value pair MAY be deleted. Vendors SHOULD NOT delete keys that were not generated by them. The deletion of an unknown key/value pair will break correlation in other systems. This mutation enables three scenarios. The first is that proxies can block certain `tracestate` keys for privacy and security concerns. The second scenario is a truncation of long `tracestate`s. Finally, vendors MAY also discard duplicate keys that were not generated by them. 382 | -------------------------------------------------------------------------------- /spec/21-http_response_header_format.md: -------------------------------------------------------------------------------- 1 | # Trace Context HTTP Response Headers Format 2 | 3 | This section describes the binding of the distributed trace context to the `traceresponse` HTTP header. 4 | 5 | ## Traceresponse Header 6 | 7 | The `traceresponse` HTTP response header field identifies a completed request in a tracing system. It has four fields: 8 | 9 | * `version` 10 | * `trace-id` 11 | * `child-id` 12 | * `trace-flags` 13 | 14 | ### Header Name 15 | 16 | Header name: `traceresponse` 17 | 18 | The header name is [ASCII case-insensitive](https://infra.spec.whatwg.org/#ascii-case-insensitive). That is, `TRACERESPONSE`, `TraceResponse`, and `traceresponse` are considered the same header. The header name is a single word; it does not contain any delimiters such as a hyphen. 19 | 20 | In order to increase interoperability across multiple protocols and encourage successful integration, tracing systems SHOULD encode the header name as [ASCII lowercase](https://infra.spec.whatwg.org/#ascii-lowercase). 21 | 22 | ### traceresponse Header Field Values 23 | 24 | This section uses the Augmented Backus-Naur Form (ABNF) notation of [[!RFC5234]], including the DIGIT rule from that document. The `DIGIT` rule defines a single number character `0`-`9`. 25 | 26 | ``` abnf 27 | HEXDIGLC = DIGIT / "a" / "b" / "c" / "d" / "e" / "f" ; lowercase hex character 28 | value = version "-" version-format 29 | ``` 30 | 31 | The dash (`-`) character is used as a delimiter between fields. 32 | 33 | #### version 34 | 35 | ``` abnf 36 | version = 2HEXDIGLC ; this document assumes version 00. Version ff is forbidden 37 | ``` 38 | 39 | Version (`version`) is an 8-bit unsigned integer value, serialized as an ASCII string with two characters. Version 255 (`"ff"`) is invalid. This document specifies version 0 (`"00"`) of the `traceresponse` header. 40 | 41 | #### version-format 42 | 43 | The following `version-format` definition is used for version `00`. 44 | 45 | ``` abnf 46 | version-format = trace-id "-" child-id "-" trace-flags 47 | trace-id = 32HEXDIGLC ; 16 bytes array identifier. All zeroes forbidden 48 | child-id = 16HEXDIGLC ; 8 bytes array identifier. All zeroes forbidden 49 | trace-flags = 2HEXDIGLC ; 8 bit flags. See below for details 50 | ``` 51 | 52 | #### trace-id 53 | 54 | The format and requirements for this are the same as those of the trace-id field in the `traceparent` request header. 55 | 56 | For details, see the trace-id section under [traceparent Header Field Values](#traceparent-header-field-values). 57 | 58 | #### child-id 59 | 60 | This is the ID of the operation of the callee (in some tracing systems, this is known as the `span-id`, where a `span` is the execution of a client request) and is used to uniquely identify an operation within a trace. It is represented as an 8-byte array, for example, `00f067aa0ba902b7`. All bytes as zero (`0000000000000000`) is considered an invalid value. 61 | 62 | Vendors MUST ignore the `traceresponse` header when the `child-id` is invalid (for example, if it contains non-lowercase hex characters). 63 | 64 | #### trace-flags 65 | 66 | Similar to the [`trace-flags` field](#trace-flags) in the `traceparent` request header, this is a hex-encoded 8-bit field that provides information about how a callee handled the trace. The same requirement to properly mask the bit field value when interpreting it applies here as well. 67 | 68 | The current version of this specification (`00`) supports only two flags: `sampled` and `random-trace-id`. 69 | 70 | These flags are recommendations given by a callee rather than strict rules to follow for three reasons: 71 | 72 | 1. An untrusted callee may be able to abuse a tracing system by setting these flags maliciously. 73 | 2. A callee may have a bug which causes the tracing system to have a problem. 74 | 3. Different load between calling and called services might force one or more participants to discard part or all of a trace. 75 | 76 | You can find more in the section [Security considerations](#security-considerations) of this specification. 77 | 78 | ##### Sampled flag 79 | 80 | When set, the least significant bit (right-most), denotes that the callee may have recorded trace data. When unset, the callee did not record trace data out-of-band. 81 | 82 | The `sampled` flag provides interoperability between tracing systems. It allows tracing systems to communicate recording decisions and enable a better experience for the customer. For example, when a SaaS load balancer service participates in a distributed trace, this service has no knowledge of the tracing system used by its callee. This service may produce records of incoming requests for monitoring or troubleshooting purposes. The `sampled` flag can be used to ensure that information about requests that were marked for recording by the callee will also be recorded by the SaaS load balancer service upstream so that the callee can troubleshoot the behavior of every recorded request. 83 | 84 | The `sampled` flag has no restrictions. 85 | 86 | The following are a set of suggestions that tracing systems SHOULD use to increase interoperability. 87 | 88 | - If a component made a definitive recording decision, this decision SHOULD be reflected in the `sampled` flag. 89 | - If a component needs to make a recording decision, it SHOULD respect the `sampled` flag value. 90 | [Security considerations](#security-considerations) SHOULD be applied to protect from abusive or malicious use of this flag. 91 | - If a component deferred or delayed the decision and only a subset of telemetry will be recorded, the `sampled` flag from the incoming `traceparent` header should be used if it is available. It should be set to `0` as the default option when the trace is initiated by this component. 92 | - If a component receives a `0` for the `sampled` flag on an incoming request, it may still decide to record a trace. In this case it SHOULD return a `sampled` flag `1` on the response so that the caller can update its sampling decision if required. 93 | 94 | There are two additional options that tracing systems MAY follow: 95 | 96 | - A component that makes a deferred or delayed recording decision may communicate the priority of a recording by setting `sampled` flag to `1` for a subset of requests. 97 | - A component may also fall back to probability sampling and set the `sampled` flag to `1` for the subset of requests. 98 | 99 | ##### Random Trace ID Flag 100 | 101 | The second least significant bit of the trace-flags field denotes the random-trace-id flag. 102 | 103 | If a trace was started by a downstream participant and it responds with the `traceresponse` HTTP header, an upstream participant can use this flag to determine if the `trace-id` was generated as per 104 | the specification for this flag. 105 | 106 | When a participant starts or restarts a trace (that is, when the participant generates a new `trace-id`), the requirements for this flag are the same as those for the random-trace-id flag in the trace-flags field in the `traceparent` request header. For details, see the section [Random Trace ID Flag](#random-trace-id-flag). 107 | 108 | A participant that continues a trace started upstream — that is, if the participant uses the `trace-id` value from an incoming `traceparent` header in its own `traceresponse` header — MUST set the `random-trace-id` flag in the `traceresponse` header to the same value that was found in the incoming `traceparent` header. 109 | 110 | A participant that continues a trace started downstream — that is, if the participant uses the `trace-id` value from a `traceresponse` header it has received — MUST set the `random-trace-id` flag in its own `traceresponse` header to the same value that was found in the `traceresponse` header from which the `trace-id` was taken. 111 | 112 | ##### Other Flags 113 | 114 | The behavior of other flags, such as (`00000100`) is not defined and is reserved for future use. Tracing systems MUST set those to zero. 115 | -------------------------------------------------------------------------------- /spec/30-processing-model.md: -------------------------------------------------------------------------------- 1 | # Processing Model 2 | 3 | This section provides a step-by-step example of a tracing vendor receiving a request with trace context headers, processing the request and then potentially forwarding it. This description can be used as a reference when implementing a trace context-compliant tracing system, middleware (like a proxy or messaging bus), or a cloud service. 4 | 5 | ## Processing Model for Working with Trace Context Request Header 6 | 7 | This processing model describes the behavior of a vendor that modifies and forwards trace context headers. How the model works depends on whether or not a `traceparent` header is received. 8 | 9 | ### No traceparent Received 10 | 11 | If no traceparent header is received: 12 | 13 | 1. The vendor checks an incoming request for a `traceparent` and a `tracestate` header. 14 | 2. Because the `traceparent` header is not received, the vendor creates a new `trace-id` and `parent-id` that represents the current request. (Note: If the vendor does not sample this request and wants to communicate that sampling decision downstream via the `sampled` flag, the vendor MAY create a `trace-id` and `parent-id` that are not associated with any actual trace data. The vendor MAY also decide to not communicate the sampling decision downstream.) 15 | 3. If a `tracestate` header is received without an accompanying `traceparent` header, it is invalid and MUST be discarded. 16 | 4. The vendor SHOULD create a new `tracestate` header and add a new key/value pair. 17 | 5. The vendor sets the `traceparent` and `tracestate` header for the outgoing request. 18 | 19 | ### A traceparent is Received 20 | 21 | If a `traceparent` header is received: 22 | 23 | 1. The vendor checks an incoming request for a `traceparent` and a `tracestate` header. 24 | 2. Because the `traceparent` header _is present_, the vendor tries to parse the version of the `traceparent` header. 25 | 1. If the _version cannot be parsed_, the vendor creates a new `traceparent` header and deletes `tracestate`. 26 | 2. If the _version number is higher_ than supported by the tracer, the vendor uses the format defined in this specification (`00`) to parse `trace-id` and `parent-id`. 27 | The vendor will only parse the `trace-flags` values supported by this version of this specification and ignore all other values. If parsing fails, the vendor creates a new `traceparent` header and deletes the `tracestate`. Vendors will set all unparsed / unknown `trace-flags` to 0 on outgoing requests. 28 | 3. If the vendor _supports the version number_, it validates `trace-id` and `parent-id`. If either `trace-id`, `parent-id` or `trace-flags` are invalid, the vendor creates a new `traceparent` header and deletes `tracestate`. 29 | 3. The vendor MAY validate the `tracestate` header. If the `tracestate` header cannot be parsed the vendor MAY discard the entire header. Invalid `tracestate` entries MAY also be discarded. 30 | 4. For each outgoing request the vendor performs the following steps: 31 | 1. The vendor MUST modify the `traceparent` header: 32 | * **Update `parent-id`:** The value of property `parent-id` MUST be set to a value representing the ID of the current operation. 33 | * **Update `sampled`:** The value of `sampled` reflects the caller's recording behavior. The value of the `sampled` flag of `trace-flags` MAY be set to `1` if the trace data is likely to be recorded or to `0` otherwise. Setting the flag is no guarantee that the trace will be recorded but increases the likeliness of end-to-end recorded traces. 34 | 35 | 2. The vendor MAY modify the `tracestate` header: 36 | * **Update a key value:** The value of any key can be updated. Modified keys MUST be moved to the beginning (left) of the list. 37 | * **Add a new key/value pair:** The new key-value pair MUST be added to the beginning (left) of the list. 38 | * **Delete a key/value pair:** Any key/value pair MAY be deleted. Vendors SHOULD NOT delete keys that weren't generated by themselves. Deletion of any key/value pair MAY break correlation in other systems. 39 | 3. The vendor sets the `traceparent` and `tracestate` header for the outgoing request. 40 | 41 | ### Alternative Processing 42 | 43 | The processing model above describes the complete set of steps for processing trace context headers. There are, however, situations when a vendor might only support a subset of the steps described above. Proxies or messaging middleware MAY decide not to modify the `traceparent` headers but remove invalid headers or add additional information to `tracestate`. 44 | 45 | ## Processing Model for Working with Trace Context Response Header 46 | 47 | This processing model describes the behavior of a tracing system that returns trace context headers. Behavior depends on the configuration of the tracing system and what information it wishes to return to the caller. 48 | 49 | ### Restarted Trace 50 | 51 | When a service is called by an untrusted third party, it may decide to restart the trace. In this case, the called service MAY return a `traceresponse` field indicating its internal `trace-id`, `span-id`, and sampling decision. 52 | 53 | Example request and response: 54 | 55 | Request 56 | ```http 57 | traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-d75597dee50b0cac-01 58 | ``` 59 | Response 60 | ```http 61 | traceresponse: 00-1baad25c36c11c1e7fbd6d122bd85db6-cab70b47728a8a99-01 62 | ``` 63 | 64 | In this example, a participant in a trace with ID `4bf92f3577b34da6a3ce929d0e0e4736` calls a third party system that collects their own internal telemetry using a new trace ID `1baad25c36c11c1e7fbd6d122bd85db6`. When the third party completes its request, it returns the new trace ID, the ID of the operation, and internal sampling decision to the caller. If there is an error with the request, the caller can include the third party's internal trace ID in a support request. 65 | 66 | ### Load Balancer Deferred Sampling 67 | 68 | When a service initially makes the decision to _not_ sample a particular request, and also makes an outbound call to another downstream service, there may be some event during the processing of that request which causes the downstream service to decide to sample after all. In this case, the downstream service may return its updated sampling decision to the caller via the `traceresponse` header. Based on this, the caller may change its sampling decision and in turn also return its updated sampling decision to its caller, and so on. In this way, as much of a trace as possible may be recovered for troubleshooting purposes even if the original sampling decision was negative. 69 | 70 | One example of this might be a load balancer which samples a random subset of requests. If the destination service encounters a problem, it may indicate that the request should be sampled by the load balancer anyway by returning a `traceresponse` with the sampled flag set. 71 | 72 | Example request and response: 73 | 74 | Request 75 | ```http 76 | traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-d75597dee50b0cac-00 77 | ``` 78 | Response 79 | ```http 80 | traceresponse: 00-4bf92f3577b34da6a3ce929d0e0e4736-828c5d0d435ba505-01 81 | ``` 82 | 83 | In this example, a caller (the load balancer) in a trace with ID `4bf92f3577b34da6a3ce929d0e0e4736` wishes to defer a sampling decision to its callee. When the callee completes the request, it returns its sampling decision to the caller. 84 | -------------------------------------------------------------------------------- /spec/40-other-protocols.md: -------------------------------------------------------------------------------- 1 | # Other Communication Protocols 2 | 3 | While trace context is defined for HTTP, the authors acknowledge it is also relevant for other communication protocols. Extensions of this specification, as well as specifications produced by external organizations, define the format of trace context serialization and deserialization for other protocols. Note that these extensions may be at a different maturity level than this specification. 4 | 5 | Please refer to the [[!trace-context-protocols-registry]] for the details of trace context implementation for other protocols. 6 | -------------------------------------------------------------------------------- /spec/50-privacy.md: -------------------------------------------------------------------------------- 1 | # Privacy Considerations 2 | 3 | Requirements to propagate headers to downstream services, as well as storing values of these headers, open up potential privacy concerns. Tracing vendors MUST NOT use `traceparent` and `tracestate` fields for any personally identifiable or otherwise sensitive information. The only purpose of these fields is to enable trace correlation. 4 | 5 | Vendors MUST assess the risk of header abuse. This section provides some considerations and initial assessment of the risk associated with storing and propagating these headers. Tracing vendors may choose to inspect and remove sensitive information from the fields before allowing the tracing system to execute code that can potentially propagate or store these fields. All mutations should, however, conform to the list of mutations defined in this specification. 6 | 7 | ## Privacy of traceparent field 8 | 9 | The `traceparent` field MUST NOT contain any personally identifiable information. One way to achieve this is to randomly generate all trace IDs using a random number generator that does not expose any personally identifiable information. Any random number generator used for generating trace IDs MUST NOT rely on any information as input or seed state that can potentially be personally identifiable. 10 | 11 | Another privacy risk of the `traceparent` field is the ability to correlate requests made as part of a single transaction. A downstream service may track and correlate two or more requests made in a single transaction and may make assumptions about the identity of the caller of a request based on information from another request. 12 | 13 | Note that these privacy concerns of the `traceparent` field are theoretical rather than practical. Some services initiating or receiving a request MAY choose to restart a `traceparent` field to eliminate those risks completely. Vendors SHOULD find a way to minimize the number of distributed trace restarts to promote interoperability of tracing vendors. Instead of restarts, different techniques may be used. For example, services may define trust boundaries of upstream and downstream connections and the level of exposure that any requests may bring. For instance, a vendor might only restart `traceparent` for authentication requests from or to external services. 14 | 15 | Services may also define an algorithm and audit mechanism to validate the randomness of incoming or outgoing random numbers in the `traceparent` field. Note that this algorithm is services-specific and not a part of this specification. One example might be a temporal algorithm where a reversible hash function is applied to the current clock time. The receiver can validate that the time is within agreed upon boundaries, meaning the random number was generated with the required algorithm and in fact doesn't contain any personally identifiable information. 16 | 17 | ## Privacy of tracestate field 18 | 19 | The `tracestate` field may contain any opaque value in any of the keys. The main purpose of this header is to provide additional vendor-specific trace-identification information across different distributed tracing systems. 20 | 21 | Vendors MUST NOT include any personally identifiable information in the `tracestate` header. 22 | 23 | Vendors extremely sensitive to personal information exposure MAY implement selective removal of values corresponding to the unknown keys. Vendors SHOULD NOT mutate the `tracestate` field, as it defeats the purpose of allowing multiple tracing systems to collaborate. 24 | 25 | ## Other risks 26 | 27 | When vendors include values from the `traceparent` and `tracestate` headers in responses, these values may inadvertently be passed to cross-origin callers. Hence, if vendors include this information in responses, they should be aware that it could be seen by systems that did not participate in the trace. 28 | -------------------------------------------------------------------------------- /spec/51-security.md: -------------------------------------------------------------------------------- 1 | # Security Considerations 2 | 3 | There are two types of potential security risks associated with this specification: information exposure and denial-of-service attacks against the vendor. 4 | 5 | Vendors relying on `traceparent` and `tracestate` headers should also follow all best practices for parsing potentially malicious headers, including checking for header length and content of header values. These practices help to avoid buffer overflow and HTML injection attacks. 6 | 7 | ## Information Exposure 8 | 9 | As mentioned in the privacy section, information in the `traceparent` and `tracestate` headers may carry information that can be considered sensitive. For example, `traceparent` may allow one request to be correlated to the data sent with another request, or the `tracestate` header may imply the version of monitoring software used by the caller. This information could potentially be used to create a larger attack. 10 | 11 | Application owners should either ensure that no proprietary or confidential information is stored in `tracestate`, or they should ensure that `tracestate` isn't present in requests to external systems. 12 | 13 | ## Denial of Service 14 | 15 | When distributed tracing is enabled on a service with a public API and naively continues any trace with the `sampled` flag set, a malicious attacker could overwhelm an application with tracing overhead, forge `trace-id` collisions that make monitoring data unusable, or run up your tracing bill with your SaaS tracing vendor. 16 | 17 | Tracing vendors and platforms should account for these situations and make sure that checks and balances are in place to protect denial of monitoring by malicious or badly authored callers. 18 | 19 | One example of such protection may be different tracing behavior for authenticated and unauthenticated requests. Various rate limiters for data recording can also be implemented. 20 | 21 | ## Other Risks 22 | 23 | Application owners need to make sure to test all code paths leading to the sending of `traceparent` and `tracestate` headers. For example, in single page browser applications, it is typical to make cross-origin requests. If one of these code paths leads to `traceparent` and `tracestate` headers being sent by cross-origin calls that are restricted using `Access-Control-Allow-Headers` [[FETCH]], it may fail. 24 | -------------------------------------------------------------------------------- /spec/60-acknowledgments.md: -------------------------------------------------------------------------------- 1 | ## Acknowledgments 2 | 3 | Thanks to Adrian Cole, Christoph Neumüller, Daniel Khan, Erika Arnold, Fabian Lange, Matthew Wear, Philippe Le Hegaret, Reiley Yang, Ted Young, Tyler Benson, and Victor Soares for their contributions to this work. 4 | -------------------------------------------------------------------------------- /spec/60-trace-id-format.md: -------------------------------------------------------------------------------- 1 | ## Considerations for trace-id field generation 2 | 3 | This section suggests some best practices to consider when platform or tracing 4 | vendor implement `trace-id` generation and propagation algorithms. These 5 | practices will ensure better interoperability of different systems. 6 | 7 | ### Uniqueness of `trace-id` 8 | 9 | The value of `trace-id` SHOULD be globally unique. This field is typically used 10 | for unique identification of a distributed trace. It is common for 11 | distributed traces to span various components, including, for example, 12 | cloud services. Cloud services tend to serve variety of clients and have a very 13 | high throughput of requests. So global uniqueness of `trace-id` is important, 14 | even when local uniqueness might seem like a good solution. 15 | 16 | ### Randomness of `trace-id` 17 | 18 | Randomly generated value of `trace-id` SHOULD be preferred over other 19 | algorithms of generating a globally unique identifiers. Randomness of `trace-id` 20 | addresses some [security](#security-considerations) and [privacy 21 | concerns](#privacy-considerations) of exposing unwanted information. Randomness 22 | also allows tracing vendors to base sampling decisions on `trace-id` field value 23 | and avoid propagating an additional sampling context. 24 | 25 | If the `random-trace-id` flag is set, at least the right-most 7 bytes of the 26 | `trace-id` MUST be selected randomly (or pseudo-randomly) with uniform distribution 27 | over the interval [0..2^56-1]. 28 | 29 | As shown in the next section, if part of the `trace-id` is nonrandom, 30 | it is important for the random part of the `trace-id` to be as far right in the 31 | `trace-id` as possible for better inter-operability with some existing systems. 32 | 33 | ### Handling `trace-id` for compliant platforms with shorter internal identifiers 34 | 35 | There are tracing systems which use a `trace-id` that is shorter than 16 bytes, 36 | which are still willing to adopt this specification. 37 | 38 | If such a system is capable of propagating a fully compliant `trace-id`, even 39 | while still requiring a shorter, non-compliant identifier for internal purposes, 40 | the system is encouraged to utilize the `tracestate` header to propagate the 41 | additional internal identifier. However, if a system would instead prefer to use 42 | the internal identifier as the basis for a fully compliant `trace-id`, it SHOULD 43 | be incorporated at the as rightmost part of a `trace-id`. For example, tracing 44 | system may receive `234a5bcd543ef3fa53ce929d0e0e4736` as a `trace-id`, however 45 | internally it will use `53ce929d0e0e4736` as an identifier. 46 | 47 | ### Interoperating with existing systems which use shorter identifiers 48 | 49 | There are tracing systems which are not capable of propagating the entire 16 50 | bytes of a `trace-id`. For better interoperability between a fully compliant 51 | systems with these existing systems, the following practices are recommended: 52 | 53 | 1. When a system creates an outbound message and needs to generate a fully 54 | compliant 16 bytes `trace-id` from a shorter identifier, it SHOULD left pad 55 | the original identifier with zeroes. For example, the identifier 56 | `53ce929d0e0e4736`, SHOULD be converted to `trace-id` value 57 | `000000000000000053ce929d0e0e4736`. If the resultant `trace-id` value does 58 | not satisfy the constraints of the `random-trace-id` flag, the flag MUST 59 | be set to `0`. 60 | 2. When a system receives an inbound message and needs to convert the 16 bytes 61 | `trace-id` to a shorter identifier, the rightmost part of `trace-id` SHOULD 62 | be used as this identifier. For instance, if the value of `trace-id` was 63 | `234a5bcd543ef3fa53ce929d0e0e4736` on an incoming request, tracing system 64 | SHOULD use identifier with the value of `53ce929d0e0e4736`. 65 | 66 | Similar transformations are expected when tracing system converts other 67 | distributed trace context propagation formats to W3C Trace Context. Shorter 68 | identifiers SHOULD be left padded with zeros when converted to 16 bytes 69 | `trace-id` and rightmost part of `trace-id` SHOULD be used as a shorter 70 | identifier. 71 | 72 | Note, many existing systems that are not capable of propagating the whole 73 | `trace-id` will not propagate `tracestate` header either. However, such system 74 | can still use `tracestate` header to propagate additional data that is known by 75 | this system. For example, some systems use two flags indicating whether 76 | distributed trace needs to be recorded or not. In this case one flag can be send 77 | as `sampled` flag of `traceparent` header and `tracestate` can be used to send 78 | and receive an additional flag. Compliant systems will propagate this flag along 79 | all other key/value pairs. Existing systems which are not capable of 80 | `tracestate` propagation will truncate all additional values from `tracestate` 81 | and only pass along that flag. 82 | -------------------------------------------------------------------------------- /spec/61-span-id-format.md: -------------------------------------------------------------------------------- 1 | ## Considerations for `span-id` field generation 2 | 3 | This section suggests some practices to consider when implementing 4 | `span-id` generation algorithms to ensure 5 | interoperability between different systems. 6 | 7 | ### Uniqueness of `span-id` 8 | 9 | The value of `span-id` SHOULD be unique within a distributed trace. 10 | If the value of `span-id` is not unique within a distributed trace, 11 | parent-child relationships between spans within the distributed trace 12 | may be ambiguous. 13 | 14 | ### Randomness of `span-id` 15 | 16 | Values of `span-id` SHOULD be randomly generated. Randomness of `span-id` 17 | addresses some [security](#security-considerations) and [privacy 18 | concerns](#privacy-considerations) of exposing unwanted information. 19 | Randomness also ensures a high probability, though not a guarantee, of 20 | uniqueness within a distributed trace. 21 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | test_data.json 3 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://img.shields.io/travis/w3c/trace-context/main.svg?label=self%20test)](https://travis-ci.org/w3c/trace-context) 2 | 3 | # W3C Distributed Tracing Validation Service 4 | 5 | ## Install 6 | * Make sure you have Python version >= 3.6.0 installed. 7 | ``` 8 | > python --version 9 | 10 | Python 3.7.0 11 | ``` 12 | * Install aiohttp package. 13 | ``` 14 | > pip install aiohttp 15 | ``` 16 | 17 | ## Implement Test Service 18 | The test harness will use HTTP POST to communicate with your test service endpoint, giving instructions via the POST body, and waiting for your service to callback to the harness. 19 | 20 | #### HTTP POST body format 21 | The HTTP POST request body from the harness will be a JSON array, each element in the array would be an object with two properties `url` and `arguments`. The test service should iterate through the JSON array, and for each element, send an HTTP POST to the specified `url`, with `arguments` as the request body. 22 | 23 | Below is a sample request from the test harness: 24 | ``` 25 | POST /test HTTP/1.1 26 | Accept: application/json 27 | Accept-Encoding: gzip, deflate 28 | Host: 127.0.0.1:5000 29 | User-Agent: Python/3.7 aiohttp/3.3.2 30 | Content-Length: 118 31 | Content-Type: application/json 32 | 33 | [ 34 | {"url": url1, "arguments": [ 35 | {"url": url2, "arguments": []} 36 | ]}, 37 | {"url": url3, "arguments": []} 38 | ] 39 | ``` 40 | 41 | ## Run Test Cases 42 | * Make sure your [test service](#implement-test-service) is running. 43 | * Run the test script against your test service endpoint (e.g. `http://127.0.0.1:5000/test`). 44 | ``` 45 | > python test.py http://127.0.0.1:5000/test 46 | ``` 47 | * After the test completed, you will get the result. 48 | ``` 49 | harness listening on http://127.0.0.1:7777 50 | test_multiple_requests (__main__.AdvancedTest) ... ok 51 | test_both_traceparent_and_tracestate_missing (__main__.TraceContextTest) ... ok 52 | test_traceparent_header_name (__main__.TraceContextTest) ... ok 53 | test_traceparent_header_name_valid_casing (__main__.TraceContextTest) ... ok 54 | test_traceparent_included_tracestate_missing (__main__.TraceContextTest) ... ok 55 | test_traceparent_trace_flags_illegal_characters (__main__.TraceContextTest) ... FAIL 56 | 57 | ====================================================================== 58 | FAIL: test_traceparent_trace_flags_illegal_characters (__main__.TraceContextTest) 59 | ---------------------------------------------------------------------- 60 | Traceback (most recent call last): 61 | File "test.py", line 130, in test_traceparent_trace_flags_illegal_characters 62 | self.assertNotEqual(trace_id, '12345678901234567890123456789012') 63 | AssertionError: '12345678901234567890123456789012' == '12345678901234567890123456789012' 64 | 65 | ---------------------------------------------------------------------- 66 | Ran 6 tests in 0.381s 67 | 68 | FAILED (failures=1) 69 | ``` 70 | * There are optional environment variables which allow you to control the harness behavior. Please read the help message from `python test.py`. 71 | ``` 72 | Usage: python test.py [patterns] 73 | 74 | Environment Variables: 75 | HARNESS_DEBUG when set, debug mode will be enabled (default to disabled) 76 | HARNESS_HOST the public host/address of the test harness (default 127.0.0.1) 77 | HARNESS_PORT the public port of the test harness (default 7777) 78 | HARNESS_TIMEOUT the timeout (in seconds) used for each test case (default 5) 79 | HARNESS_BIND_HOST the host/address which the test harness binds to (default to HARNESS_HOST) 80 | HARNESS_BIND_PORT the port which the test harness binds to (default to HARNESS_PORT) 81 | SERVICE_ENDPOINT your test service endpoint (no default value) 82 | STRICT_LEVEL the level of test strictness (default 2) 83 | SPEC_LEVEL the minimum version of the Trace Context specification being tested (default 2) 84 | 85 | Example: 86 | # Run all tests 87 | python test.py http://127.0.0.1:5000/test 88 | # Run one test 89 | python test.py http://127.0.0.1:5000/test TraceContextTest.test_both_traceparent_and_tracestate_missing 90 | # Run one test suite 91 | python test.py http://127.0.0.1:5000/test AdvancedTest 92 | # Run two test suites 93 | python test.py http://127.0.0.1:5000/test AdvancedTest TraceContext2Test 94 | # Combinations of the above 95 | python test.py http://127.0.0.1:5000/test AdvancedTest TraceContextTest.test_both_traceparent_and_tracestate_missing 96 | python test.py http://127.0.0.1:5000/test AdvancedTest TraceContextTest.test_both_traceparent_and_tracestate_missing TraceContext2Test.test_propagates_random_flag 97 | python test.py http://127.0.0.1:5000/test AdvancedTest TraceContext2Test TraceContextTest.test_both_traceparent_and_tracestate_missing 98 | 99 | Available Test Suites: 100 | TraceContextTest 101 | Trace Context Level 1 support 102 | AdvancedTest 103 | Advanced Trace Context Level 1 support 104 | TraceContext2Test 105 | Trace Context Level 2 support 106 | ``` 107 | * Alternatively, you can use the Python [unit testing framework](https://docs.python.org/3/library/unittest.html) module to run the test. 108 | ``` 109 | > python -m unittest 110 | ``` 111 | To enable verbose output: 112 | ``` 113 | > python -m unittest -v 114 | ``` 115 | Instead of running all the test cases, you can pick a specific test case: 116 | ``` 117 | > python -m unittest test.TraceContextTest.test_traceparent_header_name 118 | ``` 119 | * When the environment variable HARNESS_DEBUG is set (to any non-empty value), debug info will be dumped to the console output: 120 | ``` 121 | > python test.py http://127.0.0.1:5000/test AdvancedTest 122 | harness listening on http://127.0.0.1:7777 123 | test_multiple_requests (__main__.AdvancedTest) ... 124 | 125 | Harness trying to send the following request to your service http://127.0.0.1:5000/test 126 | 127 | POST http://127.0.0.1:5000/test HTTP/1.1 128 | traceparent: 00-12345678901234567890123456789012-1234567890123456-01 129 | 130 | [{'arguments': [], 131 | 'url': 'http://127.0.0.1:7777/callback/608bf55129ae4e4eafef75909cc47c49.0'}, 132 | {'arguments': [], 133 | 'url': 'http://127.0.0.1:7777/callback/608bf55129ae4e4eafef75909cc47c49.1'}, 134 | {'arguments': [], 135 | 'url': 'http://127.0.0.1:7777/callback/608bf55129ae4e4eafef75909cc47c49.2'}] 136 | 137 | Your service http://127.0.0.1:5000/test responded with HTTP status 200 138 | 139 | 140 | Your service http://127.0.0.1:5000/test made the following callback to harness 141 | 142 | Host: 127.0.0.1:7777 143 | User-Agent: python-requests/2.19.1 144 | Accept-Encoding: gzip, deflate 145 | Accept: */* 146 | Connection: keep-alive 147 | Content-Length: 2 148 | traceparent: 00-12345678901234567890123456789012-1e3438eafec64bdf-01 149 | 150 | Your service http://127.0.0.1:5000/test made the following callback to harness 151 | 152 | Host: 127.0.0.1:7777 153 | User-Agent: python-requests/2.19.1 154 | Accept-Encoding: gzip, deflate 155 | Accept: */* 156 | Connection: keep-alive 157 | Content-Length: 2 158 | traceparent: 00-12345678901234567890123456789012-a08ab06e8541419c-01 159 | 160 | Your service http://127.0.0.1:5000/test made the following callback to harness 161 | 162 | Host: 127.0.0.1:7777 163 | User-Agent: python-requests/2.19.1 164 | Accept-Encoding: gzip, deflate 165 | Accept: */* 166 | Connection: keep-alive 167 | Content-Length: 2 168 | traceparent: 00-12345678901234567890123456789012-a50f507837844515-01 169 | 170 | 171 | ok 172 | 173 | ---------------------------------------------------------------------- 174 | Ran 1 test in 0.204s 175 | 176 | OK 177 | ``` 178 | 179 | ## Contributing 180 | * Make sure you have Python version >= 3.6.0 installed. 181 | ``` 182 | > python --version 183 | 184 | Python 3.7.0 185 | ``` 186 | * Install aiohttp package. 187 | ``` 188 | > pip install aiohttp 189 | ``` 190 | * From the `test` folder, run self test. 191 | ``` 192 | > python self_test.py 193 | 194 | harness listening on http://127.0.0.1:7777 195 | test_multiple_requests (test.AdvancedTest) ... ok 196 | test_both_traceparent_and_tracestate_missing (test.TraceContextTest) ... ok 197 | test_traceparent_duplicated (test.TraceContextTest) ... ok 198 | test_traceparent_header_name (test.TraceContextTest) ... ok 199 | test_traceparent_header_name_valid_casing (test.TraceContextTest) ... ok 200 | test_traceparent_included_tracestate_missing (test.TraceContextTest) ... ok 201 | test_traceparent_parent_id_all_zero (test.TraceContextTest) ... ok 202 | test_traceparent_parent_id_illegal_characters (test.TraceContextTest) ... ok 203 | test_traceparent_parent_id_too_long (test.TraceContextTest) ... ok 204 | test_traceparent_parent_id_too_short (test.TraceContextTest) ... ok 205 | test_traceparent_trace_flags_illegal_characters (test.TraceContextTest) ... ok 206 | test_traceparent_trace_flags_too_long (test.TraceContextTest) ... ok 207 | test_traceparent_trace_flags_too_short (test.TraceContextTest) ... ok 208 | test_traceparent_trace_id_all_zero (test.TraceContextTest) ... ok 209 | test_traceparent_trace_id_illegal_characters (test.TraceContextTest) ... ok 210 | test_traceparent_trace_id_too_long (test.TraceContextTest) ... ok 211 | test_traceparent_trace_id_too_short (test.TraceContextTest) ... ok 212 | test_traceparent_version_0x00 (test.TraceContextTest) ... ok 213 | test_traceparent_version_0xcc (test.TraceContextTest) ... ok 214 | test_traceparent_version_0xff (test.TraceContextTest) ... ok 215 | test_traceparent_version_illegal_characters (test.TraceContextTest) ... ok 216 | test_traceparent_version_too_long (test.TraceContextTest) ... ok 217 | test_traceparent_version_too_short (test.TraceContextTest) ... ok 218 | test_tracestate_all_allowed_characters (test.TraceContextTest) ... ok 219 | test_tracestate_duplicated_keys (test.TraceContextTest) ... ok 220 | test_tracestate_empty_header (test.TraceContextTest) ... ok 221 | test_tracestate_header_name (test.TraceContextTest) ... ok 222 | test_tracestate_header_name_valid_casing (test.TraceContextTest) ... ok 223 | test_tracestate_included_traceparent_included (test.TraceContextTest) ... ok 224 | test_tracestate_included_traceparent_missing (test.TraceContextTest) ... ok 225 | test_tracestate_key_illegal_characters (test.TraceContextTest) ... ok 226 | test_tracestate_key_illegal_vendor_format (test.TraceContextTest) ... ok 227 | test_tracestate_key_length_limit (test.TraceContextTest) ... ok 228 | test_tracestate_member_count_limit (test.TraceContextTest) ... ok 229 | test_tracestate_multiple_headers_different_keys (test.TraceContextTest) ... ok 230 | test_tracestate_ows_handling (test.TraceContextTest) ... ok 231 | test_tracestate_trailing_ows (test.TraceContextTest) ... ok 232 | test_ctor (tracecontext.test_traceparent.BaseTraceparentTest) ... ok 233 | test_ctor_default (tracecontext.test_traceparent.BaseTraceparentTest) ... ok 234 | test_ctor_with_variadic_arguments (tracecontext.test_traceparent.BaseTraceparentTest) ... ok 235 | test_from_string (tracecontext.test_traceparent.BaseTraceparentTest) ... ok 236 | test_repr (tracecontext.test_traceparent.BaseTraceparentTest) ... ok 237 | test_set_parent_id (tracecontext.test_traceparent.BaseTraceparentTest) ... ok 238 | test_set_trace_id (tracecontext.test_traceparent.BaseTraceparentTest) ... ok 239 | test_parent_id_limit (tracecontext.test_traceparent.BaseTraceparentTest) ... ok 240 | test_str (tracecontext.test_traceparent.BaseTraceparentTest) ... ok 241 | test_trace_flags_limit (tracecontext.test_traceparent.BaseTraceparentTest) ... ok 242 | test_trace_id_limit (tracecontext.test_traceparent.BaseTraceparentTest) ... ok 243 | test_version_limit (tracecontext.test_traceparent.BaseTraceparentTest) ... ok 244 | test_ctor (tracecontext.test_traceparent.TraceparentTest) ... ok 245 | test_ctor_default (tracecontext.test_traceparent.TraceparentTest) ... ok 246 | test_from_string (tracecontext.test_traceparent.TraceparentTest) ... ok 247 | test_repr (tracecontext.test_traceparent.TraceparentTest) ... ok 248 | test_set_parent_id (tracecontext.test_traceparent.TraceparentTest) ... ok 249 | test_set_trace_id (tracecontext.test_traceparent.TraceparentTest) ... ok 250 | test_set_version (tracecontext.test_traceparent.TraceparentTest) ... ok 251 | test_str (tracecontext.test_traceparent.TraceparentTest) ... ok 252 | test_all_allowed_chars (tracecontext.test_tracestate.TestTracestate) ... ok 253 | test_cctor (tracecontext.test_tracestate.TestTracestate) ... ok 254 | test_ctor_kwargs (tracecontext.test_tracestate.TestTracestate) ... ok 255 | test_ctor_no_arg (tracecontext.test_tracestate.TestTracestate) ... ok 256 | test_ctor_with_dict (tracecontext.test_tracestate.TestTracestate) ... ok 257 | test_ctor_with_string (tracecontext.test_tracestate.TestTracestate) ... ok 258 | test_delimiter (tracecontext.test_tracestate.TestTracestate) ... ok 259 | test_getitem (tracecontext.test_tracestate.TestTracestate) ... ok 260 | test_method_from_string (tracecontext.test_tracestate.TestTracestate) ... ok 261 | test_method_is_valid (tracecontext.test_tracestate.TestTracestate) ... ok 262 | test_method_repr (tracecontext.test_tracestate.TestTracestate) ... ok 263 | test_pop (tracecontext.test_tracestate.TestTracestate) ... ok 264 | test_setitem (tracecontext.test_tracestate.TestTracestate) ... ok 265 | 266 | ---------------------------------------------------------------------- 267 | Ran 70 tests in 1.358s 268 | 269 | OK 270 | ``` 271 | ## Strictness levels 272 | 273 | The test harness supports different levels of strictness. It can be configured via env variable `STRICT_LEVEL`: 274 | 275 | * `STRICT_LEVEL=2`: All tests included 276 | * `STRICT_LEVEL=1`: Validation of `tracestate` size constraint validations are excluded, while tracestate propagation is still tested. 277 | -------------------------------------------------------------------------------- /test/client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import json 4 | import uuid 5 | from urllib.error import HTTPError 6 | from urllib.request import HTTPHandler, OpenerDirector, Request 7 | 8 | __all__ = ['TestClient'] 9 | 10 | class TestClient(object): 11 | def __init__(self, host, port, timeout = 5): 12 | self.host = host 13 | self.port = port 14 | self.timeout = timeout 15 | 16 | opener_director = OpenerDirector() 17 | opener_director.add_handler(HTTPHandler()) 18 | 19 | def send_request(self, url, headers = {}, arguments = None): 20 | if not isinstance(arguments, bytes): 21 | arguments = bytes(json.dumps(arguments), 'ascii') 22 | request = Request(method = 'POST', url = url, headers = headers, data = arguments) 23 | with self.opener_director.open(request, timeout = self.timeout) as response: 24 | if response.status != 200: 25 | raise HTTPError(url, response.status, response.msg, response.headers, None) 26 | return json.loads(str(response.read(), 'ascii')) 27 | 28 | class Scope(object): 29 | def __init__(self, harness): 30 | self.harness = harness 31 | self.id = uuid.uuid4().hex 32 | 33 | def __enter__(self): 34 | return self 35 | 36 | def __exit__(self, type, value, traceback): 37 | pass 38 | 39 | def __repr__(self): 40 | return '{}({})'.format(type(self).__name__, repr(self.id)) 41 | 42 | def send_request(self, arguments = None): 43 | return self.harness.send_request( 44 | url = 'http://{}:{}/{}'.format(self.harness.host, self.harness.port, self.id), 45 | headers = {'Accept': 'application/json', 'Content-Type': 'application/json'}, 46 | arguments = arguments) 47 | 48 | def url(self, path = ''): 49 | return 'http://{}:{}/{}.{}'.format(self.harness.host, self.harness.port, self.id, path) 50 | 51 | def scope(self): 52 | return TestClient.Scope(self) 53 | 54 | if __name__ == '__main__': 55 | client = TestClient(host = '127.0.0.1', port = 7777, timeout = 5) 56 | with client.scope() as scope: 57 | response = scope.send_request() 58 | -------------------------------------------------------------------------------- /test/self_test.py: -------------------------------------------------------------------------------- 1 | import json 2 | from http.server import BaseHTTPRequestHandler, HTTPServer 3 | from threading import Thread 4 | from tracecontext import BaseTraceparent, Traceparent, Tracestate 5 | from urllib.request import HTTPHandler, OpenerDirector, Request 6 | 7 | test_data = [] 8 | 9 | class DemoServer(HTTPServer): 10 | def __init__(self, host = '127.0.0.1', port = None, timeout = 5): 11 | self.host = host 12 | self.port = port 13 | self.timeout = timeout 14 | self.worker_thread = Thread(target = self.loop) 15 | if not port: 16 | self.port = 5000 17 | while self.port < 65535: 18 | try: 19 | super().__init__((self.host, self.port), DemoServer.RequestHandler) 20 | break 21 | except OSError as err: 22 | if port: 23 | raise 24 | self.port += 1 25 | 26 | def __enter__(self): 27 | self.start() 28 | return self 29 | 30 | def __exit__(self, type, value, traceback): 31 | self.stop() 32 | 33 | def loop(self): 34 | self.serve_forever() 35 | 36 | def start(self): 37 | self.worker_thread.start() 38 | 39 | def stop(self): 40 | self.shutdown() 41 | self.worker_thread.join() 42 | 43 | class RequestHandler(BaseHTTPRequestHandler): 44 | opener_director = OpenerDirector() 45 | opener_director.add_handler(HTTPHandler()) 46 | server_version = 'DemoServer/0.1' 47 | 48 | def do_POST(self): 49 | global test_data 50 | 51 | self.send_response(200) 52 | arguments = json.loads(str(self.rfile.read(int(self.headers['Content-Length'])), 'ascii')) 53 | 54 | traceparent = None 55 | tracestate = Tracestate() 56 | 57 | test_data.append({ 58 | 'headers': self.get_headers('traceparent') + self.get_headers('tracestate'), 59 | }) 60 | 61 | try: 62 | temp_traceparent = BaseTraceparent.from_string(self.get_header('traceparent')) 63 | if temp_traceparent.version == 0: 64 | if temp_traceparent._residue: 65 | raise ValueError('illegal traceparent format') 66 | traceparent = Traceparent(0, temp_traceparent.trace_id, temp_traceparent.parent_id, temp_traceparent.trace_flags) 67 | test_data[-1]['is_traceparent_valid'] = True 68 | except ValueError: 69 | test_data[-1]['is_traceparent_valid'] = False 70 | 71 | try: 72 | header = self.get_header('tracestate', commaSeparated = True) 73 | if header: 74 | tracestate = Tracestate(header) 75 | if test_data[-1]['is_traceparent_valid']: 76 | test_data[-1]['is_tracestate_valid'] = True 77 | except ValueError: 78 | # if tracestate is malformed, reuse the traceparent instead of restart the trace 79 | # traceparent = Traceparent() 80 | test_data[-1]['is_tracestate_valid'] = False 81 | 82 | if traceparent is None: 83 | # if traceparent is malformed, discard tracestate 84 | traceparent = Traceparent() 85 | tracestate = Tracestate() 86 | 87 | for item in arguments: 88 | headers = {} 89 | headers['traceparent'] = str(Traceparent(0, traceparent.trace_id, None, traceparent.trace_flags)) 90 | if tracestate.is_valid(): 91 | headers['tracestate'] = str(tracestate) 92 | request = Request(method = 'POST', url = item['url'], headers = headers, data = bytes(json.dumps(item['arguments']), 'ascii')) 93 | with self.opener_director.open(request, timeout = self.timeout) as response: 94 | pass 95 | self.end_headers() 96 | 97 | def get_headers(self, name): 98 | name = name.lower() 99 | headers = filter(lambda kv: kv[0].lower() == name, self.headers.items()) 100 | return tuple(headers) 101 | 102 | def get_header(self, name, commaSeparated = False): 103 | headers = self.get_headers(name) 104 | # https://httpwg.org/specs/rfc9110.html#fields.values 105 | # remove the leading whitespace and trailing whitespace 106 | headers = map(lambda kv: kv[1].strip(' \t'), headers) 107 | headers = tuple(headers) 108 | 109 | if not headers: 110 | return None 111 | if commaSeparated: 112 | return ','.join(headers) 113 | if len(headers) == 1: 114 | return headers[0] 115 | raise ValueError('multiple header {} not allowed'.format(name)) 116 | 117 | def log_message(self, format, *args): 118 | pass 119 | 120 | if __name__ == '__main__': 121 | import os 122 | import subprocess 123 | import sys 124 | with DemoServer() as server: 125 | os.environ['SERVICE_ENDPOINT'] = 'http://{}:{}/'.format(server.host, server.port) 126 | errno = subprocess.call(['python', '-m', 'unittest', '-v'] + sys.argv[1:]) 127 | ofile = open("test_data.json", "w") 128 | ofile.write('[\n' + ',\n'.join(map(lambda x: '\t' + json.dumps(x), test_data)) + '\n' + ']' + '\n') 129 | ofile.close() 130 | sys.exit(errno) 131 | -------------------------------------------------------------------------------- /test/server.py: -------------------------------------------------------------------------------- 1 | from aiohttp import ClientSession, ClientTimeout, ContentTypeError, web 2 | from multidict import MultiDict 3 | 4 | class AsyncTestServer(object): 5 | scopes = {} 6 | 7 | def __init__(self, host, port, timeout = 5): 8 | self.host = host 9 | self.port = port 10 | self.timeout = ClientTimeout(total = timeout) 11 | self.app = web.Application() 12 | self.app.add_routes([ 13 | web.post('/{scope}', self.scope_handler), 14 | ]) 15 | 16 | async def start(self): 17 | self.runner = web.AppRunner(self.app) 18 | await self.runner.setup() 19 | self.site = web.TCPSite(self.runner, self.host, self.port) 20 | await self.site.start() 21 | print('harness listening on http://%s:%s'%(self.host, self.port)) 22 | 23 | async def stop(self): 24 | await self.runner.cleanup() 25 | 26 | async def scope_handler(self, request): 27 | scope_id = request.match_info['scope'].split('.', maxsplit = 1) 28 | callback_id = None if len(scope_id) == 1 else scope_id[1] 29 | scope_id = scope_id[0] 30 | arguments = await request.json() 31 | scope = None 32 | if callback_id: 33 | scope = self.scopes[scope_id] 34 | scope[callback_id] = { 35 | 'headers': list(request.headers.items()), 36 | 'arguments': arguments, 37 | } 38 | else: 39 | scope = { 40 | 'headers': list(request.headers.items()), 41 | 'arguments': arguments, 42 | 'results': [], 43 | } 44 | self.scopes[scope_id] = scope 45 | if not arguments: 46 | return web.json_response(None) 47 | if not isinstance(arguments, list): 48 | arguments = [arguments] 49 | for action in arguments: 50 | headers = [['Accept', 'application/json']] 51 | if 'headers' in action: 52 | headers += action['headers'] 53 | async with ClientSession(headers = headers, timeout = self.timeout) as session: 54 | arguments = [] 55 | if 'arguments' in action: 56 | arguments = action['arguments'] or [] 57 | result = {} 58 | result['url'] = action['url'] 59 | scope['results'].append(result) 60 | try: 61 | async with session.post(action['url'], json = arguments) as response: 62 | result['status'] = response.status 63 | result['headers'] = list(response.headers.items()) 64 | result['body'] = await response.json(content_type = 'application/json') 65 | except ContentTypeError as err: 66 | result['body'] = await response.text() 67 | except Exception as err: 68 | result['exception'] = type(err).__name__ 69 | result['msg'] = str(err) 70 | if not callback_id: 71 | del self.scopes[scope_id] 72 | return web.json_response(scope) 73 | 74 | class TestServer(object): 75 | def __init__(self, host, port, timeout = 5): 76 | import asyncio 77 | from threading import Thread 78 | self.loop = asyncio.get_event_loop() 79 | self.server = AsyncTestServer(host, port, timeout) 80 | self.thread = Thread(target = self.monitor) 81 | self.run = True 82 | 83 | def monitor(self): 84 | import asyncio 85 | while self.run: 86 | self.loop.run_until_complete(asyncio.sleep(0.2)) 87 | 88 | def start(self): 89 | self.loop.run_until_complete(self.server.start()) 90 | self.thread.start() 91 | 92 | def stop(self): 93 | self.run = False 94 | self.thread.join() 95 | self.loop.run_until_complete(self.server.stop()) 96 | 97 | def __enter__(self): 98 | self.start() 99 | return self 100 | 101 | def __exit__(self, type, value, traceback): 102 | self.stop() 103 | 104 | if __name__ == '__main__': 105 | import sys 106 | host = '127.0.0.1' 107 | port = 7777 108 | if len(sys.argv) >= 2: 109 | host = sys.argv[1] 110 | if len(sys.argv) >= 3: 111 | port = int(sys.argv[2]) 112 | with TestServer(host = host, port = port) as server: 113 | input('Press Enter to quit...') 114 | -------------------------------------------------------------------------------- /test/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | import unittest 6 | from client import TestClient 7 | from server import TestServer 8 | from tracecontext import Traceparent, Tracestate 9 | 10 | client = None 11 | server = None 12 | 13 | def environ(name, default = None): 14 | if not name in os.environ: 15 | if default: 16 | return default 17 | else: 18 | raise EnvironmentError('environment variable {} is not defined'.format(name)) 19 | return os.environ[name] 20 | 21 | DEFAULT_SPEC_LEVEL = '1' 22 | DEFAULT_STRICT_LEVEL = '2' 23 | 24 | STRICT_LEVEL = int(environ('STRICT_LEVEL', DEFAULT_STRICT_LEVEL)) 25 | SPEC_LEVEL = int(environ('SPEC_LEVEL', DEFAULT_SPEC_LEVEL)) 26 | print('STRICT_LEVEL: {}'.format(STRICT_LEVEL)) 27 | print('SPEC_LEVEL: {}'.format(SPEC_LEVEL)) 28 | 29 | def setUpModule(): 30 | global client 31 | global server 32 | environ('SERVICE_ENDPOINT') 33 | client = client or TestClient(host = '127.0.0.1', port = 7777, timeout = 5) 34 | server = server or TestServer(host = '127.0.0.1', port = 7777, timeout = 3) 35 | server.start() 36 | with client.scope() as scope: 37 | response = scope.send_request() 38 | 39 | def tearDownModule(): 40 | server.stop() 41 | 42 | class TestBase(unittest.TestCase): 43 | import re 44 | traceparent_name_re = re.compile(r'^traceparent$', re.IGNORECASE) 45 | traceparent_format = r'^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$' 46 | traceparent_format_re = re.compile(traceparent_format) 47 | tracestate_name_re = re.compile(r'^tracestate$', re.IGNORECASE) 48 | 49 | def make_request(self, headers, count = 1): 50 | import pprint 51 | with client.scope() as scope: 52 | arguments = { 53 | 'url': environ('SERVICE_ENDPOINT'), 54 | 'headers': headers, 55 | 'arguments': [], 56 | } 57 | for idx in range(count): 58 | arguments['arguments'].append({'url': scope.url(str(idx)), 'arguments': []}) 59 | response = scope.send_request(arguments = arguments) 60 | verbose = ['', ''] 61 | verbose.append('Harness trying to send the following request to your service {0}'.format(arguments['url'])) 62 | verbose.append('') 63 | verbose.append('POST {} HTTP/1.1'.format(arguments['url'])) 64 | for key, value in arguments['headers']: 65 | verbose.append('{}: {}'.format(key, value)) 66 | verbose.append('') 67 | verbose.append(pprint.pformat(arguments['arguments'])) 68 | verbose.append('') 69 | results = response['results'][0] 70 | if 'exception' in results: 71 | verbose.append('Harness got an exception {}'.format(results['exception'])) 72 | verbose.append('') 73 | verbose.append(results['msg']) 74 | else: 75 | verbose.append('Your service {} responded with HTTP status {}'.format(arguments['url'], results['status'])) 76 | verbose.append('') 77 | for key, value in results['headers']: 78 | verbose.append('{}: {}'.format(key, value)) 79 | verbose.append('') 80 | if isinstance(results['body'], str): 81 | verbose.append(results['body']) 82 | else: 83 | verbose.append(pprint.pformat(results['body'])) 84 | for idx in range(count): 85 | if str(idx) in response: 86 | verbose.append('Your service {} made the following callback to harness'.format(arguments['url'])) 87 | verbose.append('') 88 | for key, value in response[str(idx)]['headers']: 89 | verbose.append('{}: {}'.format(key, value)) 90 | verbose.append('') 91 | verbose.append('') 92 | verbose = os.linesep.join(verbose) 93 | if 'HARNESS_DEBUG' in os.environ: 94 | print(verbose) 95 | result = [] 96 | for idx in range(count): 97 | self.assertTrue(str(idx) in response, 'your test service failed to make a callback to the test harness {}'.format(verbose)) 98 | result.append(response[str(idx)]) 99 | return result 100 | 101 | def get_traceparent(self, headers): 102 | retval = [] 103 | for key, value in headers: 104 | if self.traceparent_name_re.match(key): 105 | retval.append((key, value)) 106 | self.assertEqual(len(retval), 1, 'expect one traceparent header, got {} {!r}'.format('more' if retval else 'zero', retval)) 107 | return Traceparent.from_string(retval[0][1]) 108 | 109 | def get_tracestate(self, headers): 110 | tracestate = Tracestate() 111 | for key, value in headers: 112 | if self.tracestate_name_re.match(key): 113 | tracestate.from_string(value) 114 | return tracestate 115 | 116 | def make_single_request_and_get_tracecontext(self, headers): 117 | headers = self.make_request(headers)[0]['headers'] 118 | return (self.get_traceparent(headers), self.get_tracestate(headers)) 119 | 120 | def assert_bit_set(self, value, n, message = None): 121 | ''' 122 | assert that the nth position bit of value is set where n == 0 is the least significant bit 123 | ''' 124 | msg = "expected value 0x{:02x} to have #{}th bit set but it did not".format(value, n) 125 | if message: 126 | msg += " : " + message 127 | self.assertEqual(value & (1 << n - 1), (1 << n - 1), msg) 128 | 129 | class TraceContextTest(TestBase): 130 | def test_both_traceparent_and_tracestate_missing(self): 131 | ''' 132 | harness sends a request without traceparent or tracestate 133 | expects a valid traceparent from the output header 134 | ''' 135 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([]) 136 | 137 | def test_traceparent_included_tracestate_missing(self): 138 | ''' 139 | harness sends a request with traceparent but without tracestate 140 | expects a valid traceparent from the output header, with the same trace_id but different parent_id 141 | ''' 142 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 143 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-01'], 144 | ]) 145 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 146 | self.assertNotEqual(traceparent.parent_id.hex(), '1234567890123456') 147 | 148 | def test_traceparent_duplicated(self): 149 | ''' 150 | harness sends a request with two traceparent headers 151 | expects a valid traceparent from the output header, with a newly generated trace_id 152 | ''' 153 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 154 | ['traceparent', '00-12345678901234567890123456789011-1234567890123456-01'], 155 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-01'], 156 | ]) 157 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789011') 158 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 159 | 160 | def test_traceparent_header_name(self): 161 | ''' 162 | harness sends an invalid traceparent using wrong names 163 | expects a valid traceparent from the output header, with a newly generated trace_id 164 | ''' 165 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 166 | ['trace-parent', '00-12345678901234567890123456789012-1234567890123456-01'], 167 | ]) 168 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 169 | 170 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 171 | ['trace.parent', '00-12345678901234567890123456789012-1234567890123456-01'], 172 | ]) 173 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 174 | 175 | def test_traceparent_header_name_valid_casing(self): 176 | ''' 177 | harness sends a valid traceparent using different combination of casing 178 | expects a valid traceparent from the output header 179 | ''' 180 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 181 | ['TraceParent', '00-12345678901234567890123456789012-1234567890123456-01'], 182 | ]) 183 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 184 | 185 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 186 | ['TrAcEpArEnT', '00-12345678901234567890123456789012-1234567890123456-01'], 187 | ]) 188 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 189 | 190 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 191 | ['TRACEPARENT', '00-12345678901234567890123456789012-1234567890123456-01'], 192 | ]) 193 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 194 | 195 | def test_traceparent_version_0x00(self): 196 | ''' 197 | harness sends an invalid traceparent with extra trailing characters 198 | expects a valid traceparent from the output header, with a newly generated trace_id 199 | ''' 200 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 201 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-01.'], 202 | ]) 203 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 204 | 205 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 206 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-01-what-the-future-will-be-like'], 207 | ]) 208 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 209 | 210 | def test_traceparent_version_0xcc(self): 211 | ''' 212 | harness sends an valid traceparent with future version 204 (0xcc) 213 | expects a valid traceparent from the output header with the same trace_id 214 | ''' 215 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 216 | ['traceparent', 'cc-12345678901234567890123456789012-1234567890123456-01'], 217 | ]) 218 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 219 | 220 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 221 | ['traceparent', 'cc-12345678901234567890123456789012-1234567890123456-01-what-the-future-will-be-like'], 222 | ]) 223 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 224 | 225 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 226 | ['traceparent', 'cc-12345678901234567890123456789012-1234567890123456-01.what-the-future-will-be-like'], 227 | ]) 228 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 229 | 230 | def test_traceparent_version_0xff(self): 231 | ''' 232 | harness sends an invalid traceparent with version 255 (0xff) 233 | expects a valid traceparent from the output header, with a newly generated trace_id 234 | ''' 235 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 236 | ['traceparent', 'ff-12345678901234567890123456789012-1234567890123456-01'], 237 | ]) 238 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 239 | 240 | def test_traceparent_version_illegal_characters(self): 241 | ''' 242 | harness sends an invalid traceparent with illegal characters in version 243 | expects a valid traceparent from the output header, with a newly generated trace_id 244 | ''' 245 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 246 | ['traceparent', '.0-12345678901234567890123456789012-1234567890123456-01'], 247 | ]) 248 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 249 | 250 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 251 | ['traceparent', '0.-12345678901234567890123456789012-1234567890123456-01'], 252 | ]) 253 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 254 | 255 | def test_traceparent_version_too_long(self): 256 | ''' 257 | harness sends an invalid traceparent with version more than 2 HEXDIG 258 | expects a valid traceparent from the output header, with a newly generated trace_id 259 | ''' 260 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 261 | ['traceparent', '000-12345678901234567890123456789012-1234567890123456-01'], 262 | ]) 263 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 264 | 265 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 266 | ['traceparent', '0000-12345678901234567890123456789012-1234567890123456-01'], 267 | ]) 268 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 269 | 270 | def test_traceparent_version_too_short(self): 271 | ''' 272 | harness sends an invalid traceparent with version less than 2 HEXDIG 273 | expects a valid traceparent from the output header, with a newly generated trace_id 274 | ''' 275 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 276 | ['traceparent', '0-12345678901234567890123456789012-1234567890123456-01'], 277 | ]) 278 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 279 | 280 | def test_traceparent_trace_id_all_zero(self): 281 | ''' 282 | harness sends an invalid traceparent with trace_id = 00000000000000000000000000000000 283 | expects a valid traceparent from the output header, with a newly generated trace_id 284 | ''' 285 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 286 | ['traceparent', '00-00000000000000000000000000000000-1234567890123456-01'], 287 | ]) 288 | self.assertNotEqual(traceparent.trace_id.hex(), '00000000000000000000000000000000') 289 | 290 | def test_traceparent_trace_id_illegal_characters(self): 291 | ''' 292 | harness sends an invalid traceparent with illegal characters in trace_id 293 | expects a valid traceparent from the output header, with a newly generated trace_id 294 | ''' 295 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 296 | ['traceparent', '00-.2345678901234567890123456789012-1234567890123456-01'], 297 | ]) 298 | self.assertNotEqual(traceparent.trace_id.hex(), '.2345678901234567890123456789012') 299 | 300 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 301 | ['traceparent', '00-1234567890123456789012345678901.-1234567890123456-01'], 302 | ]) 303 | self.assertNotEqual(traceparent.trace_id.hex(), '1234567890123456789012345678901.') 304 | 305 | def test_traceparent_trace_id_too_long(self): 306 | ''' 307 | harness sends an invalid traceparent with trace_id more than 32 HEXDIG 308 | expects a valid traceparent from the output header, with a newly generated trace_id 309 | ''' 310 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 311 | ['traceparent', '00-123456789012345678901234567890123-1234567890123456-01'], 312 | ]) 313 | self.assertNotEqual(traceparent.trace_id.hex(), '123456789012345678901234567890123') 314 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 315 | self.assertNotEqual(traceparent.trace_id.hex(), '23456789012345678901234567890123') 316 | 317 | def test_traceparent_trace_id_too_short(self): 318 | ''' 319 | harness sends an invalid traceparent with trace_id less than 32 HEXDIG 320 | expects a valid traceparent from the output header, with a newly generated trace_id 321 | ''' 322 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 323 | ['traceparent', '00-1234567890123456789012345678901-1234567890123456-01'], 324 | ]) 325 | self.assertNotEqual(traceparent.trace_id.hex(), '1234567890123456789012345678901') 326 | 327 | def test_traceparent_parent_id_all_zero(self): 328 | ''' 329 | harness sends an invalid traceparent with parent_id = 0000000000000000 330 | expects a valid traceparent from the output header, with a newly generated trace_id 331 | ''' 332 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 333 | ['traceparent', '00-12345678901234567890123456789012-0000000000000000-01'], 334 | ]) 335 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 336 | 337 | def test_traceparent_parent_id_illegal_characters(self): 338 | ''' 339 | harness sends an invalid traceparent with illegal characters in parent_id 340 | expects a valid traceparent from the output header, with a newly generated trace_id 341 | ''' 342 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 343 | ['traceparent', '00-12345678901234567890123456789012-.234567890123456-01'], 344 | ]) 345 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 346 | 347 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 348 | ['traceparent', '00-12345678901234567890123456789012-123456789012345.-01'], 349 | ]) 350 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 351 | 352 | def test_traceparent_parent_id_too_long(self): 353 | ''' 354 | harness sends an invalid traceparent with parent_id more than 16 HEXDIG 355 | expects a valid traceparent from the output header, with a newly generated trace_id 356 | ''' 357 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 358 | ['traceparent', '00-12345678901234567890123456789012-12345678901234567-01'], 359 | ]) 360 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 361 | 362 | def test_traceparent_parent_id_too_short(self): 363 | ''' 364 | harness sends an invalid traceparent with parent_id less than 16 HEXDIG 365 | expects a valid traceparent from the output header, with a newly generated trace_id 366 | ''' 367 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 368 | ['traceparent', '00-12345678901234567890123456789012-123456789012345-01'], 369 | ]) 370 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 371 | 372 | def test_traceparent_trace_flags_illegal_characters(self): 373 | ''' 374 | harness sends an invalid traceparent with illegal characters in trace_flags 375 | expects a valid traceparent from the output header, with a newly generated trace_id 376 | ''' 377 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 378 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-.0'], 379 | ]) 380 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 381 | 382 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 383 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-0.'], 384 | ]) 385 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 386 | 387 | def test_traceparent_trace_flags_too_long(self): 388 | ''' 389 | harness sends an invalid traceparent with trace_flags more than 2 HEXDIG 390 | expects a valid traceparent from the output header, with a newly generated trace_id 391 | ''' 392 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 393 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-001'], 394 | ]) 395 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 396 | 397 | def test_traceparent_trace_flags_too_short(self): 398 | ''' 399 | harness sends an invalid traceparent with trace_flags less than 2 HEXDIG 400 | expects a valid traceparent from the output header, with a newly generated trace_id 401 | ''' 402 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 403 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-1'], 404 | ]) 405 | self.assertNotEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 406 | 407 | def test_traceparent_ows_handling(self): 408 | ''' 409 | harness sends an valid traceparent with heading and trailing OWS 410 | expects a valid traceparent from the output header 411 | ''' 412 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 413 | ['traceparent', ' 00-12345678901234567890123456789012-1234567890123456-01'], 414 | ]) 415 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 416 | 417 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 418 | ['traceparent', '\t00-12345678901234567890123456789012-1234567890123456-01'], 419 | ]) 420 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 421 | 422 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 423 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-01 '], 424 | ]) 425 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 426 | 427 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 428 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-01\t'], 429 | ]) 430 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 431 | 432 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 433 | ['traceparent', '\t 00-12345678901234567890123456789012-1234567890123456-01 \t'], 434 | ]) 435 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 436 | 437 | def test_tracestate_included_traceparent_missing(self): 438 | ''' 439 | harness sends a request with tracestate but without traceparent 440 | expects a valid traceparent from the output header 441 | expects the tracestate to be discarded 442 | ''' 443 | traceparent, tracestate1 = self.make_single_request_and_get_tracecontext([ 444 | ['tracestate', 'foo=1'], 445 | ]) 446 | traceparent, tracestate2 = self.make_single_request_and_get_tracecontext([ 447 | ['tracestate', 'foo=1,bar=2'], 448 | ]) 449 | self.assertEqual(len(tracestate1), len(tracestate2)) 450 | 451 | def test_tracestate_included_traceparent_included(self): 452 | ''' 453 | harness sends a request with both tracestate and traceparent 454 | expects a valid traceparent from the output header with the same trace_id 455 | expects the tracestate to be inherited 456 | ''' 457 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 458 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 459 | ['tracestate', 'foo=1,bar=2'], 460 | ]) 461 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 462 | self.assertIn("foo", tracestate) 463 | self.assertIn("bar", tracestate) 464 | self.assertEqual(tracestate['foo'], '1') 465 | self.assertEqual(tracestate['bar'], '2') 466 | 467 | def test_tracestate_header_name(self): 468 | ''' 469 | harness sends an invalid tracestate using wrong names 470 | expects the tracestate to be discarded 471 | ''' 472 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 473 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 474 | ['trace-state', 'foo=1'], 475 | ]) 476 | self.assertRaises(KeyError, lambda: tracestate['foo']) 477 | 478 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 479 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 480 | ['trace.state', 'foo=1'], 481 | ]) 482 | self.assertRaises(KeyError, lambda: tracestate['foo']) 483 | 484 | def test_tracestate_header_name_valid_casing(self): 485 | ''' 486 | harness sends a valid tracestate using different combination of casing 487 | expects the tracestate to be inherited 488 | ''' 489 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 490 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 491 | ['TraceState', 'foo=1'], 492 | ]) 493 | self.assertIn('foo', tracestate) 494 | self.assertEqual(tracestate['foo'], '1') 495 | 496 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 497 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 498 | ['TrAcEsTaTe', 'foo=1'], 499 | ]) 500 | self.assertIn('foo', tracestate) 501 | self.assertEqual(tracestate['foo'], '1') 502 | 503 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 504 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 505 | ['TRACESTATE', 'foo=1'], 506 | ]) 507 | self.assertIn('foo', tracestate) 508 | self.assertEqual(tracestate['foo'], '1') 509 | 510 | def test_tracestate_empty_header(self): 511 | ''' 512 | harness sends a request with empty tracestate header 513 | expects the empty tracestate to be discarded 514 | ''' 515 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 516 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 517 | ['tracestate', ''], 518 | ]) 519 | self.assertTrue(not tracestate or tracestate != '') 520 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 521 | 522 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 523 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 524 | ['tracestate', 'foo=1'], 525 | ['tracestate', ''], 526 | ]) 527 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 528 | self.assertIn('foo', tracestate) 529 | self.assertEqual(tracestate['foo'], '1') 530 | 531 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 532 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 533 | ['tracestate', ''], 534 | ['tracestate', 'foo=1'], 535 | ]) 536 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 537 | self.assertIn('foo', tracestate) 538 | self.assertEqual(tracestate['foo'], '1') 539 | 540 | def test_tracestate_multiple_headers_different_keys(self): 541 | ''' 542 | harness sends a request with multiple tracestate headers, each contains different set of keys 543 | expects a combined tracestate 544 | ''' 545 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 546 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 547 | ['tracestate', 'foo=1,bar=2'], 548 | ['tracestate', 'rojo=1,congo=2'], 549 | ['tracestate', 'baz=3'], 550 | ]) 551 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 552 | self.assertTrue('foo=1' in str(tracestate)) 553 | self.assertTrue('bar=2' in str(tracestate)) 554 | self.assertTrue('rojo=1' in str(tracestate)) 555 | self.assertTrue('congo=2' in str(tracestate)) 556 | self.assertTrue('baz=3' in str(tracestate)) 557 | self.assertTrue(str(tracestate).index('foo=1') < str(tracestate).index('bar=2')) 558 | self.assertTrue(str(tracestate).index('bar=2') < str(tracestate).index('rojo=1')) 559 | self.assertTrue(str(tracestate).index('rojo=1') < str(tracestate).index('congo=2')) 560 | self.assertTrue(str(tracestate).index('congo=2') < str(tracestate).index('baz=3')) 561 | 562 | @unittest.skipIf(STRICT_LEVEL < 2, "strict") 563 | def test_tracestate_duplicated_keys(self): 564 | ''' 565 | harness sends a request with an invalid tracestate header with duplicated keys 566 | expects the tracestate to be inherited, and the duplicated keys to be either kept as-is or one of them 567 | to be discarded 568 | ''' 569 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 570 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 571 | ['tracestate', 'foo=1,foo=1'], 572 | ]) 573 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 574 | self.assertTrue('foo=1' in str(tracestate)) 575 | 576 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 577 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 578 | ['tracestate', 'foo=1,foo=2'], 579 | ]) 580 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 581 | self.assertTrue('foo=1' in str(tracestate) or 'foo=2' in str(tracestate)) 582 | 583 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 584 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 585 | ['tracestate', 'foo=1'], 586 | ['tracestate', 'foo=1'], 587 | ]) 588 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 589 | self.assertTrue('foo=1' in str(tracestate)) 590 | 591 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 592 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 593 | ['tracestate', 'foo=1'], 594 | ['tracestate', 'foo=2'], 595 | ]) 596 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 597 | self.assertTrue('foo=1' in str(tracestate) or 'foo=2' in str(tracestate)) 598 | 599 | def test_tracestate_all_allowed_characters(self): 600 | ''' 601 | harness sends a request with a valid tracestate header with all legal characters 602 | expects the tracestate to be inherited 603 | ''' 604 | key_without_vendor = ''.join([ 605 | ''.join(map(chr, range(0x61, 0x7A + 1))), # lcalpha 606 | '0123456789', # DIGIT 607 | '_', 608 | '-', 609 | '*', 610 | '/', 611 | ]) 612 | key_with_vendor = key_without_vendor + '@a-z0-9_-*/' 613 | value = ''.join([ 614 | ''.join(map(chr, range(0x20, 0x2B + 1))), 615 | ''.join(map(chr, range(0x2D, 0x3C + 1))), 616 | ''.join(map(chr, range(0x3E, 0x7E + 1))), 617 | ]) 618 | 619 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 620 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 621 | ['tracestate', key_without_vendor + '=' + value], 622 | ]) 623 | self.assertIn(key_without_vendor, tracestate) 624 | self.assertEqual(tracestate[key_without_vendor], value) 625 | 626 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 627 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 628 | ['tracestate', key_with_vendor + '=' + value], 629 | ]) 630 | self.assertIn(key_with_vendor, tracestate) 631 | self.assertEqual(tracestate[key_with_vendor], value) 632 | 633 | def test_tracestate_ows_handling(self): 634 | ''' 635 | harness sends a request with a valid tracestate header with OWS 636 | expects the tracestate to be inherited 637 | ''' 638 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 639 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 640 | ['tracestate', 'foo=1 \t , \t bar=2, \t baz=3'], 641 | ]) 642 | self.assertIn('foo', tracestate) 643 | self.assertIn('bar', tracestate) 644 | self.assertIn('baz', tracestate) 645 | self.assertEqual(tracestate['foo'], '1') 646 | self.assertEqual(tracestate['bar'], '2') 647 | self.assertEqual(tracestate['baz'], '3') 648 | 649 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 650 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 651 | ['tracestate', 'foo=1\t \t,\t \tbar=2,\t \tbaz=3'], 652 | ]) 653 | self.assertIn('foo', tracestate) 654 | self.assertIn('bar', tracestate) 655 | self.assertIn('baz', tracestate) 656 | self.assertEqual(tracestate['foo'], '1') 657 | self.assertEqual(tracestate['bar'], '2') 658 | self.assertEqual(tracestate['baz'], '3') 659 | 660 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 661 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 662 | ['tracestate', ' foo=1'], 663 | ]) 664 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 665 | self.assertIn('foo', tracestate) 666 | self.assertEqual(tracestate['foo'], '1') 667 | 668 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 669 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 670 | ['tracestate', '\tfoo=1'], 671 | ]) 672 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 673 | self.assertIn('foo', tracestate) 674 | self.assertEqual(tracestate['foo'], '1') 675 | 676 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 677 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 678 | ['tracestate', 'foo=1 '], 679 | ]) 680 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 681 | self.assertIn('foo', tracestate) 682 | self.assertEqual(tracestate['foo'], '1') 683 | 684 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 685 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 686 | ['tracestate', 'foo=1\t'], 687 | ]) 688 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 689 | self.assertIn('foo', tracestate) 690 | self.assertEqual(tracestate['foo'], '1') 691 | 692 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 693 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 694 | ['tracestate', '\t foo=1 \t'], 695 | ]) 696 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 697 | self.assertIn('foo', tracestate) 698 | self.assertEqual(tracestate['foo'], '1') 699 | 700 | @unittest.skipIf(STRICT_LEVEL < 2, "strict") 701 | def test_tracestate_key_illegal_characters(self): 702 | ''' 703 | harness sends a request with an invalid tracestate header with illegal key 704 | expects the tracestate to be discarded 705 | ''' 706 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 707 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 708 | ['tracestate', 'foo =1'], 709 | ]) 710 | self.assertRaises(KeyError, lambda: tracestate['foo ']) 711 | 712 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 713 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 714 | ['tracestate', 'FOO=1'], 715 | ]) 716 | self.assertRaises(KeyError, lambda: tracestate['FOO']) 717 | 718 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 719 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 720 | ['tracestate', 'foo.bar=1'], 721 | ]) 722 | self.assertRaises(KeyError, lambda: tracestate['foo.bar']) 723 | 724 | @unittest.skipIf(STRICT_LEVEL < 2, "strict") 725 | def test_tracestate_key_illegal_vendor_format(self): 726 | ''' 727 | harness sends a request with an invalid tracestate header with illegal vendor format 728 | expects the tracestate to be discarded 729 | ''' 730 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 731 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 732 | ['tracestate', 'foo@=1,bar=2'], 733 | ]) 734 | self.assertRaises(KeyError, lambda: tracestate['bar']) 735 | 736 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 737 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 738 | ['tracestate', '@foo=1,bar=2'], 739 | ]) 740 | self.assertRaises(KeyError, lambda: tracestate['bar']) 741 | 742 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 743 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 744 | ['tracestate', 'foo@@bar=1,bar=2'], 745 | ]) 746 | self.assertRaises(KeyError, lambda: tracestate['bar']) 747 | 748 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 749 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 750 | ['tracestate', 'foo@bar@baz=1,bar=2'], 751 | ]) 752 | self.assertRaises(KeyError, lambda: tracestate['bar']) 753 | 754 | @unittest.skipIf(STRICT_LEVEL < 2, "strict") 755 | def test_tracestate_member_count_limit(self): 756 | ''' 757 | harness sends a request with a valid tracestate header with 32 list members 758 | expects the tracestate to be inherited 759 | 760 | harness sends a request with an invalid tracestate header with 33 list members 761 | expects the tracestate to be discarded 762 | ''' 763 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 764 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 765 | ['tracestate', 'bar01=01,bar02=02,bar03=03,bar04=04,bar05=05,bar06=06,bar07=07,bar08=08,bar09=09,bar10=10'], 766 | ['tracestate', 'bar11=11,bar12=12,bar13=13,bar14=14,bar15=15,bar16=16,bar17=17,bar18=18,bar19=19,bar20=20'], 767 | ['tracestate', 'bar21=21,bar22=22,bar23=23,bar24=24,bar25=25,bar26=26,bar27=27,bar28=28,bar29=29,bar30=30'], 768 | ['tracestate', 'bar31=31,bar32=32'], 769 | ]) 770 | self.assertIn('bar01', tracestate) 771 | self.assertEqual(tracestate['bar01'], '01') 772 | self.assertEqual(len(tracestate), 32) 773 | 774 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 775 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 776 | ['tracestate', 'bar01=01,bar02=02,bar03=03,bar04=04,bar05=05,bar06=06,bar07=07,bar08=08,bar09=09,bar10=10'], 777 | ['tracestate', 'bar11=11,bar12=12,bar13=13,bar14=14,bar15=15,bar16=16,bar17=17,bar18=18,bar19=19,bar20=20'], 778 | ['tracestate', 'bar21=21,bar22=22,bar23=23,bar24=24,bar25=25,bar26=26,bar27=27,bar28=28,bar29=29,bar30=30'], 779 | ['tracestate', 'bar31=31,bar32=32,bar33=33'], 780 | ]) 781 | self.assertRaises(KeyError, lambda: tracestate['bar01']) 782 | 783 | @unittest.skipIf(STRICT_LEVEL < 2, "strict") 784 | def test_tracestate_key_length_limit(self): 785 | ''' 786 | harness sends tracestate header with a key of 256 and 257 characters 787 | harness sends tracestate header with a key of 14 and 15 characters in the vendor section 788 | harness sends tracestate header with a key of 241 and 242 characters in the tenant section 789 | ''' 790 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 791 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 792 | ['tracestate', 'foo=1'], 793 | ['tracestate', 'z' * 256 + '=1'], 794 | ]) 795 | self.assertIn('foo', tracestate) 796 | self.assertEqual(tracestate['foo'], '1') 797 | 798 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 799 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 800 | ['tracestate', 'foo=1'], 801 | ['tracestate', 'z' * 257 + '=1'], 802 | ]) 803 | self.assertRaises(KeyError, lambda: tracestate['foo']) 804 | 805 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 806 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 807 | ['tracestate', 'foo=1'], 808 | ['tracestate', 't' * 241 + '@' + 'v' * 14 + '=1'], 809 | ]) 810 | self.assertIn('foo', tracestate) 811 | self.assertEqual(tracestate['foo'], '1') 812 | 813 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 814 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 815 | ['tracestate', 'foo=1'], 816 | ['tracestate', 't' * 242 + '@v=1'], 817 | ]) 818 | self.assertRaises(KeyError, lambda: tracestate['foo']) 819 | 820 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 821 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 822 | ['tracestate', 'foo=1'], 823 | ['tracestate', 't@' + 'v' * 15 + '=1'], 824 | ]) 825 | self.assertRaises(KeyError, lambda: tracestate['foo']) 826 | 827 | @unittest.skipIf(STRICT_LEVEL < 2, "strict") 828 | def test_tracestate_value_illegal_characters(self): 829 | ''' 830 | harness sends a request with an invalid tracestate header with illegal value format 831 | expects the tracestate to be discarded 832 | ''' 833 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 834 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 835 | ['tracestate', 'foo=bar=baz'], 836 | ]) 837 | self.assertRaises(KeyError, lambda: tracestate['foo']) 838 | 839 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 840 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'], 841 | ['tracestate', 'foo=,bar=3'], 842 | ]) 843 | self.assertRaises(KeyError, lambda: tracestate['foo']) 844 | self.assertRaises(KeyError, lambda: tracestate['bar']) 845 | 846 | class AdvancedTest(TestBase): 847 | def test_multiple_requests_with_valid_traceparent(self): 848 | ''' 849 | harness sends a valid traceparent and asks vendor service to callback multiple times 850 | expects the trace_id to be inherited by all the callbacks 851 | ''' 852 | trace_ids = set() 853 | parent_ids = set() 854 | for response in self.make_request([ 855 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-01'], 856 | ], 3): 857 | traceparent = self.get_traceparent(response['headers']) 858 | trace_ids.add(traceparent.trace_id.hex()) 859 | parent_ids.add(traceparent.parent_id.hex()) 860 | self.assertEqual(len(trace_ids), 1) 861 | self.assertTrue('12345678901234567890123456789012' in trace_ids) 862 | self.assertEqual(len(parent_ids), 3) 863 | 864 | def test_multiple_requests_without_traceparent(self): 865 | ''' 866 | harness asks vendor service to callback multiple times 867 | expects a different parent_id each time 868 | ''' 869 | trace_ids = set() 870 | parent_ids = set() 871 | for response in self.make_request([], 3): 872 | traceparent = self.get_traceparent(response['headers']) 873 | trace_ids.add(traceparent.trace_id.hex()) 874 | parent_ids.add(traceparent.parent_id.hex()) 875 | self.assertEqual(len(parent_ids), 3) 876 | 877 | def test_multiple_requests_with_illegal_traceparent(self): 878 | ''' 879 | harness sends an invalid traceparent and asks vendor service to callback multiple times 880 | expects new trace_id(s) generated 881 | ''' 882 | trace_ids = set() 883 | parent_ids = set() 884 | for response in self.make_request([ 885 | ['traceparent', '00-00000000000000000000000000000000-1234567890123456-01'], 886 | ], 3): 887 | traceparent = self.get_traceparent(response['headers']) 888 | trace_ids.add(traceparent.trace_id.hex()) 889 | parent_ids.add(traceparent.parent_id.hex()) 890 | self.assertFalse('00000000000000000000000000000000' in trace_ids) 891 | self.assertEqual(len(parent_ids), 3) 892 | 893 | @unittest.skipIf(SPEC_LEVEL < 2, "Trace Context Level 2") 894 | class TraceContext2Test(TestBase): 895 | def test_propagates_random_flag(self): 896 | ''' 897 | harness sends a request with a traceparent with the random flag set 898 | expects a valid traceparent from the output header, with the random flag set 899 | ''' 900 | traceparent, tracestate = self.make_single_request_and_get_tracecontext([ 901 | ['traceparent', '00-12345678901234567890123456789012-1234567890123456-02'], 902 | ]) 903 | self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012') 904 | self.assertNotEqual(traceparent.parent_id.hex(), '1234567890123456') 905 | self.assert_bit_set(traceparent.trace_flags, 2, "random flag not set") 906 | 907 | if __name__ == '__main__': 908 | if len(sys.argv) >= 2: 909 | os.environ['SERVICE_ENDPOINT'] = sys.argv[1] 910 | if not 'SERVICE_ENDPOINT' in os.environ: 911 | print(''' 912 | Usage: python {0} [patterns] 913 | 914 | Environment Variables: 915 | HARNESS_DEBUG when set, debug mode will be enabled (default to disabled) 916 | HARNESS_HOST the public host/address of the test harness (default 127.0.0.1) 917 | HARNESS_PORT the public port of the test harness (default 7777) 918 | HARNESS_TIMEOUT the timeout (in seconds) used for each test case (default 5) 919 | HARNESS_BIND_HOST the host/address which the test harness binds to (default to HARNESS_HOST) 920 | HARNESS_BIND_PORT the port which the test harness binds to (default to HARNESS_PORT) 921 | SERVICE_ENDPOINT your test service endpoint (no default value) 922 | STRICT_LEVEL the level of test strictness (default 2) 923 | SPEC_LEVEL the minimum version of the Trace Context specification being tested (default 1) 924 | 925 | Example: 926 | # Run all tests 927 | python {0} http://127.0.0.1:5000/test 928 | # Run one test 929 | python {0} http://127.0.0.1:5000/test TraceContextTest.test_both_traceparent_and_tracestate_missing 930 | # Run one test suite 931 | python {0} http://127.0.0.1:5000/test AdvancedTest 932 | # Run two test suites 933 | python {0} http://127.0.0.1:5000/test AdvancedTest TraceContext2Test 934 | # Combinations of the above 935 | python {0} http://127.0.0.1:5000/test AdvancedTest TraceContextTest.test_both_traceparent_and_tracestate_missing 936 | python {0} http://127.0.0.1:5000/test AdvancedTest TraceContextTest.test_both_traceparent_and_tracestate_missing TraceContext2Test.test_propagates_random_flag 937 | python {0} http://127.0.0.1:5000/test AdvancedTest TraceContext2Test TraceContextTest.test_both_traceparent_and_tracestate_missing 938 | 939 | Available Test Suites: 940 | TraceContextTest 941 | Trace Context Level 1 support 942 | AdvancedTest 943 | Advanced Trace Context Level 1 support 944 | TraceContext2Test 945 | Trace Context Level 2 support 946 | '''.strip().format(sys.argv[0]), file = sys.stderr) 947 | exit(-1) 948 | 949 | if not 'SPEC_LEVEL' in os.environ: 950 | print('Warning: The environment variable SPEC_LEVEL has not been set. Defaulting to specification level %s. Consider setting SPEC_LEVEL explicitly if your implementation is based on a different specification level. In the future, the default specification level of this test suite may be raised.' % (DEFAULT_SPEC_LEVEL)) 951 | 952 | host = environ('HARNESS_HOST', '127.0.0.1') 953 | port = environ('HARNESS_PORT', '7777') 954 | timeout = environ('HARNESS_TIMEOUT', '5') 955 | bind_host = environ('HARNESS_BIND_HOST', host) 956 | bind_port = environ('HARNESS_BIND_PORT', port) 957 | client = TestClient(host = host, port = int(port), timeout = int(timeout) + 1) 958 | server = TestServer(host = bind_host, port = int(bind_port), timeout = int(timeout)) 959 | 960 | suite = unittest.TestSuite() 961 | loader = unittest.TestLoader() 962 | if len(sys.argv) > 2: 963 | for name in sys.argv[2:]: 964 | suite.addTests(loader.loadTestsFromName(name, module = sys.modules[__name__])) 965 | else: 966 | suite.addTests(loader.loadTestsFromModule(sys.modules[__name__])) 967 | result = unittest.TextTestRunner(verbosity = 2).run(suite) 968 | sys.exit(len(result.errors) + len(result.failures)) 969 | -------------------------------------------------------------------------------- /test/tracecontext/__init__.py: -------------------------------------------------------------------------------- 1 | from .traceparent import BaseTraceparent, Traceparent 2 | from .tracestate import Tracestate 3 | 4 | __all__ = ( 5 | 'BaseTraceparent', 6 | 'Traceparent', 7 | 'Tracestate', 8 | ) 9 | -------------------------------------------------------------------------------- /test/tracecontext/test_traceparent.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from tracecontext import BaseTraceparent, Traceparent 3 | 4 | class BaseTraceparentTest(unittest.TestCase): 5 | def test_ctor_default(self): 6 | traceparent = BaseTraceparent() 7 | self.assertEqual(traceparent.version, 0) 8 | self.assertEqual(traceparent.trace_id, b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0') 9 | self.assertEqual(traceparent.parent_id, b'\0\0\0\0\0\0\0\0') 10 | self.assertEqual(traceparent.trace_flags, 0) 11 | 12 | def test_ctor(self): 13 | self.assertRaises(ValueError, lambda: BaseTraceparent(version = 0xff)) 14 | 15 | def test_ctor_with_variadic_arguments(self): 16 | traceparent = BaseTraceparent(0, None, None, 0, 'foo', 'bar', 'baz') 17 | 18 | def test_version_limit(self): 19 | traceparent = BaseTraceparent(version = 0) 20 | self.assertEqual(traceparent.version, 0) 21 | 22 | traceparent = BaseTraceparent(version = 254) 23 | self.assertEqual(traceparent.version, 254) 24 | 25 | self.assertRaises(ValueError, lambda: BaseTraceparent(version = -1)) 26 | self.assertRaises(ValueError, lambda: BaseTraceparent(version = 255)) 27 | 28 | def test_trace_id_limit(self): 29 | traceparent = BaseTraceparent(trace_id = b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0') 30 | self.assertEqual(traceparent.trace_id, b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0') 31 | 32 | self.assertRaises(ValueError, lambda: BaseTraceparent(trace_id = b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0')) 33 | self.assertRaises(ValueError, lambda: BaseTraceparent(trace_id = b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0')) 34 | 35 | def test_parent_id_limit(self): 36 | traceparent = BaseTraceparent(parent_id = b'\0\0\0\0\0\0\0\0') 37 | self.assertEqual(traceparent.parent_id, b'\0\0\0\0\0\0\0\0') 38 | 39 | self.assertRaises(ValueError, lambda: BaseTraceparent(parent_id = b'\0\0\0\0\0\0\0')) 40 | self.assertRaises(ValueError, lambda: BaseTraceparent(parent_id = b'\0\0\0\0\0\0\0\0\0')) 41 | 42 | def test_trace_flags_limit(self): 43 | traceparent = BaseTraceparent(trace_flags = 0) 44 | self.assertEqual(traceparent.trace_flags, 0) 45 | 46 | traceparent = BaseTraceparent(trace_flags = 0xff) 47 | self.assertEqual(traceparent.trace_flags, 0xff) 48 | 49 | self.assertRaises(ValueError, lambda: BaseTraceparent(trace_flags = -1)) 50 | self.assertRaises(ValueError, lambda: BaseTraceparent(trace_flags = 0xff + 1)) 51 | 52 | def test_from_string(self): 53 | traceparent = BaseTraceparent.from_string('00-12345678901234567890123456789012-1234567890123456-00') 54 | traceparent = BaseTraceparent.from_string('01-12345678901234567890123456789012-1234567890123456-00') 55 | traceparent = BaseTraceparent.from_string('02-12345678901234567890123456789012-1234567890123456-00') 56 | traceparent = BaseTraceparent.from_string('cc-12345678901234567890123456789012-1234567890123456-00') 57 | self.assertRaises(ValueError, lambda: BaseTraceparent.from_string('ff-12345678901234567890123456789012-1234567890123456-00')) 58 | 59 | traceparent = BaseTraceparent.from_string('00-00000000000000000000000000000000-1234567890123456-00') 60 | traceparent = BaseTraceparent.from_string('00-12345678901234567890123456789012-1234567890123456-00') 61 | traceparent = BaseTraceparent.from_string('00-ffffffffffffffffffffffffffffffff-1234567890123456-00') 62 | 63 | traceparent = BaseTraceparent.from_string('00-12345678901234567890123456789012-0000000000000000-00') 64 | traceparent = BaseTraceparent.from_string('00-12345678901234567890123456789012-1234567890123456-00') 65 | traceparent = BaseTraceparent.from_string('00-12345678901234567890123456789012-ffffffffffffffff-00') 66 | 67 | traceparent = BaseTraceparent.from_string('00-12345678901234567890123456789012-1234567890123456-00') 68 | traceparent = BaseTraceparent.from_string('00-12345678901234567890123456789012-1234567890123456-01') 69 | traceparent = BaseTraceparent.from_string('00-12345678901234567890123456789012-1234567890123456-02') 70 | traceparent = BaseTraceparent.from_string('00-12345678901234567890123456789012-1234567890123456-03') 71 | traceparent = BaseTraceparent.from_string('00-12345678901234567890123456789012-1234567890123456-ff') 72 | 73 | self.assertRaises(ValueError, lambda: BaseTraceparent.from_string('0-12345678901234567890123456789012-1234567890123456-00')) 74 | self.assertRaises(ValueError, lambda: BaseTraceparent.from_string('000-12345678901234567890123456789012-1234567890123456-00')) 75 | self.assertRaises(ValueError, lambda: BaseTraceparent.from_string('00-1234567890123456789012345678901-1234567890123456-00')) 76 | self.assertRaises(ValueError, lambda: BaseTraceparent.from_string('00-123456789012345678901234567890123-1234567890123456-00')) 77 | self.assertRaises(ValueError, lambda: BaseTraceparent.from_string('00-12345678901234567890123456789012-123456789012345-00')) 78 | self.assertRaises(ValueError, lambda: BaseTraceparent.from_string('00-12345678901234567890123456789012-12345678901234567-00')) 79 | 80 | def test_repr(self): 81 | string = '12-12345678901234567890123456789012-1234567890123456-ff' 82 | traceparent = BaseTraceparent.from_string('12-12345678901234567890123456789012-1234567890123456-ff') 83 | self.assertEqual(repr(traceparent), 'BaseTraceparent({})'.format(repr(string))) 84 | 85 | def test_str(self): 86 | string = '12-12345678901234567890123456789012-1234567890123456-ff' 87 | traceparent = BaseTraceparent.from_string('12-12345678901234567890123456789012-1234567890123456-ff') 88 | self.assertEqual(str(traceparent), string) 89 | 90 | def test_set_trace_id(self): 91 | traceparent = BaseTraceparent() 92 | traceparent.set_trace_id(None) 93 | traceparent.set_trace_id(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff') 94 | self.assertRaises(ValueError, lambda: traceparent.set_trace_id('fffffffffffffffffffffffffffffff')) 95 | traceparent.set_trace_id('ffffffffffffffffffffffffffffffff') 96 | self.assertRaises(ValueError, lambda: traceparent.set_trace_id('fffffffffffffffffffffffffffffffff')) 97 | 98 | def test_set_parent_id(self): 99 | traceparent = BaseTraceparent() 100 | traceparent.set_parent_id(None) 101 | traceparent.set_parent_id(b'\xff\xff\xff\xff\xff\xff\xff\xff') 102 | self.assertRaises(ValueError, lambda: traceparent.set_parent_id('fffffffffffffff')) 103 | traceparent.set_parent_id('ffffffffffffffff') 104 | self.assertRaises(ValueError, lambda: traceparent.set_parent_id('fffffffffffffffff')) 105 | 106 | class TraceparentTest(unittest.TestCase): 107 | def test_ctor_default(self): 108 | traceparent = Traceparent() 109 | self.assertEqual(traceparent.version, 0) 110 | self.assertNotEqual(traceparent.trace_id, b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0') 111 | self.assertNotEqual(traceparent.parent_id, b'\0\0\0\0\0\0\0\0') 112 | self.assertEqual(traceparent.trace_flags, 0) 113 | 114 | def test_ctor(self): 115 | self.assertRaises(ValueError, lambda: Traceparent(version = 1)) 116 | self.assertRaises(ValueError, lambda: Traceparent(version = 0xff)) 117 | self.assertRaises(ValueError, lambda: Traceparent(trace_id = b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0')) 118 | self.assertRaises(ValueError, lambda: Traceparent(parent_id = b'\0\0\0\0\0\0\0\0')) 119 | 120 | def test_from_string(self): 121 | traceparent = Traceparent.from_string('00-12345678901234567890123456789012-1234567890123456-00') 122 | traceparent = Traceparent.from_string('00-12345678901234567890123456789012-1234567890123456-01') 123 | traceparent = Traceparent.from_string('00-12345678901234567890123456789012-1234567890123456-02') 124 | traceparent = Traceparent.from_string('00-12345678901234567890123456789012-1234567890123456-03') 125 | traceparent = Traceparent.from_string('00-12345678901234567890123456789012-1234567890123456-ff') 126 | self.assertRaises(ValueError, lambda: Traceparent.from_string('01-12345678901234567890123456789012-1234567890123456-00')) 127 | self.assertRaises(ValueError, lambda: Traceparent.from_string('02-12345678901234567890123456789012-1234567890123456-00')) 128 | self.assertRaises(ValueError, lambda: Traceparent.from_string('cc-12345678901234567890123456789012-1234567890123456-00')) 129 | self.assertRaises(ValueError, lambda: Traceparent.from_string('ff-12345678901234567890123456789012-1234567890123456-00')) 130 | self.assertRaises(ValueError, lambda: Traceparent.from_string('00-00000000000000000000000000000000-1234567890123456-00')) 131 | self.assertRaises(ValueError, lambda: Traceparent.from_string('00-12345678901234567890123456789012-0000000000000000-00')) 132 | 133 | def test_repr(self): 134 | string = '00-12345678901234567890123456789012-1234567890123456-ff' 135 | traceparent = Traceparent.from_string('00-12345678901234567890123456789012-1234567890123456-ff') 136 | self.assertEqual(repr(traceparent), 'Traceparent({})'.format(repr(string))) 137 | 138 | def test_str(self): 139 | string = '00-12345678901234567890123456789012-1234567890123456-ff' 140 | traceparent = Traceparent.from_string('00-12345678901234567890123456789012-1234567890123456-ff') 141 | self.assertEqual(str(traceparent), string) 142 | 143 | def test_set_version(self): 144 | traceparent = Traceparent() 145 | traceparent.set_version(0) 146 | self.assertRaises(ValueError, lambda: traceparent.set_version(1)) 147 | 148 | def test_set_trace_id(self): 149 | traceparent = Traceparent() 150 | self.assertRaises(ValueError, lambda: traceparent.set_trace_id(None)) 151 | 152 | def test_set_parent_id(self): 153 | traceparent = Traceparent() 154 | self.assertRaises(ValueError, lambda: traceparent.set_parent_id(None)) 155 | 156 | if __name__ == '__main__': 157 | unittest.main() 158 | -------------------------------------------------------------------------------- /test/tracecontext/test_tracestate.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from tracecontext import Tracestate 3 | 4 | class TestTracestate(unittest.TestCase): 5 | def test_ctor_no_arg(self): 6 | state = Tracestate() 7 | self.assertEqual(state.to_string(), '') 8 | 9 | def test_ctor_with_dict(self): 10 | state = Tracestate({'foo': '1'}) 11 | self.assertEqual(state.to_string(), 'foo=1') 12 | 13 | def test_ctor_kwargs(self): 14 | state = Tracestate(foo = '1', bar = '2', baz = '3') 15 | self.assertEqual(state.to_string(), 'foo=1,bar=2,baz=3') 16 | 17 | def test_ctor_with_string(self): 18 | state = Tracestate('foo=1,bar=2,baz=3') 19 | self.assertEqual(state.to_string(), 'foo=1,bar=2,baz=3') 20 | 21 | self.assertRaises(ValueError, lambda: Tracestate('foobarbaz')) 22 | 23 | def test_cctor(self): 24 | state = Tracestate(Tracestate('foo=1,bar=2,baz=3')) 25 | self.assertEqual(state.to_string(), 'foo=1,bar=2,baz=3') 26 | 27 | def test_all_allowed_chars(self): 28 | header = ''.join([ 29 | # key 30 | ''.join(map(chr, range(0x61, 0x7A + 1))), # lcalpha 31 | '0123456789', # DIGIT 32 | '_', 33 | '-', 34 | '*', 35 | '/', 36 | # "=" 37 | '=', 38 | # value 39 | ''.join(map(chr, range(0x20, 0x2B + 1))), 40 | ''.join(map(chr, range(0x2D, 0x3C + 1))), 41 | ''.join(map(chr, range(0x3E, 0x7E + 1))), 42 | ]) 43 | state = Tracestate(header) 44 | self.assertEqual(state.to_string(), header) 45 | 46 | def test_delimiter(self): 47 | state = Tracestate('foo=1, \t bar=2') 48 | self.assertEqual(state.to_string(), 'foo=1,bar=2') 49 | 50 | state = Tracestate('foo=1,\t \tbar=2') 51 | self.assertEqual(state.to_string(), 'foo=1,bar=2') 52 | 53 | def test_getitem(self): 54 | state = Tracestate({'foo': '1'}) 55 | self.assertEqual(state['foo'], '1') 56 | 57 | def test_method_from_string(self): 58 | state = Tracestate() 59 | state.from_string('foo=1') 60 | state.from_string('bar=2') 61 | state.from_string('baz=3') 62 | self.assertEqual(state.to_string(), 'foo=1,bar=2,baz=3') 63 | 64 | # test load order 65 | state = Tracestate() 66 | state.from_string('baz=3') 67 | state.from_string('bar=2') 68 | state.from_string('foo=1') 69 | self.assertNotEqual(state.to_string(), 'foo=1,bar=2,baz=3') 70 | 71 | def test_method_is_valid(self): 72 | state = Tracestate() 73 | 74 | # empty state not allowed 75 | self.assertFalse(state.is_valid()) 76 | 77 | state['foo'] = 'x' * 256 78 | self.assertTrue(state.is_valid()) 79 | 80 | # exceeds 512 bytes 81 | state['bar'] = 'x' * 256 82 | self.assertFalse(state.is_valid()) 83 | self.assertTrue(Tracestate(state.to_string()[:512]).is_valid()) 84 | self.assertFalse(Tracestate(state.to_string()[:513]).is_valid()) 85 | 86 | def test_method_repr(self): 87 | state = Tracestate('foo=1, bar=2, baz=3') 88 | self.assertEqual(repr(state), "Tracestate('foo=1,bar=2,baz=3')") 89 | 90 | def test_pop(self): 91 | state = Tracestate('foo=1,bar=2,baz=3') 92 | self.assertEqual(state.pop(), ('baz', '3')) 93 | self.assertEqual(state.to_string(), 'foo=1,bar=2') 94 | self.assertEqual(state.pop(), ('bar', '2')) 95 | self.assertEqual(state.to_string(), 'foo=1') 96 | self.assertEqual(state.pop(), ('foo', '1')) 97 | self.assertEqual(state.to_string(), '') 98 | # raise KeyError exception while trying to pop from nothing 99 | self.assertRaises(KeyError, lambda: state.pop()) 100 | 101 | def test_setitem(self): 102 | state = Tracestate(bar = '0') 103 | state['foo'] = '1' 104 | state['bar'] = '2' 105 | state['baz'] = '3' 106 | self.assertEqual(state.to_string(), 'baz=3,bar=2,foo=1') 107 | 108 | # key SHOULD be string 109 | self.assertRaises(ValueError, lambda: state.__setitem__(123, 'abc')) 110 | # value SHOULD NOT be empty string 111 | self.assertRaises(ValueError, lambda: state.__setitem__('', 'abc')) 112 | # key SHOULD NOT have uppercase 113 | self.assertRaises(ValueError, lambda: state.__setitem__('FOO', 'abc')) 114 | 115 | # key with vendor format 116 | state['special@vendor'] = 'abracadabra' 117 | self.assertRaises(ValueError, lambda: state.__setitem__('special@', 'abracadabra')) 118 | self.assertRaises(ValueError, lambda: state.__setitem__('@vendor', 'abracadabra')) 119 | 120 | # value SHOULD be string 121 | self.assertRaises(ValueError, lambda: state.__setitem__('FOO', 123)) 122 | # value SHOULD NOT be empty string 123 | self.assertRaises(ValueError, lambda: state.__setitem__('foo', '')) 124 | # value SHOULD NOT contain invalid character 125 | self.assertRaises(ValueError, lambda: state.__setitem__('foo', 'bar=baz')) 126 | 127 | state['foo'] = 'x' * 256 128 | # throw if value exceeds 256 bytes 129 | self.assertRaises(ValueError, lambda: state.__setitem__('foo', 'x' * 257)) 130 | 131 | if __name__ == '__main__': 132 | unittest.main() 133 | -------------------------------------------------------------------------------- /test/tracecontext/traceparent.py: -------------------------------------------------------------------------------- 1 | import re 2 | import uuid 3 | 4 | class BaseTraceparent(object): 5 | _VERSION_FORMAT_RE = re.compile('^[0-9a-f]{2}$') 6 | _TRACE_ID_FORMAT_RE = re.compile('^[0-9a-f]{32}$') 7 | _PARENT_ID_FORMAT_RE = re.compile('^[0-9a-f]{16}$') 8 | _TRACE_FLAGS_FORMAT_RE = re.compile('^[0-9a-f]{2}$') 9 | _ZERO_TRACE_ID = b'\0' * 16 10 | _ZERO_PARENT_ID = b'\0' * 8 11 | 12 | def __init__(self, version = 0, trace_id = None, parent_id = None, trace_flags = 0, *_residue): 13 | self.version = version 14 | self.trace_id = trace_id 15 | self.parent_id = parent_id 16 | self.trace_flags = trace_flags 17 | self._residue = _residue 18 | 19 | def __repr__(self): 20 | return '{}({!r})'.format(type(self).__name__, str(self)) 21 | 22 | def __str__(self): 23 | return self.to_string() 24 | 25 | @classmethod 26 | def from_string(cls, value): 27 | if not isinstance(value, str): 28 | raise ValueError('value must be a string') 29 | value = value.split('-') 30 | return cls(*value) 31 | 32 | def to_string(self): 33 | retval = '{}-{}-{}-{}'.format(self._version.hex(), self._trace_id.hex(), self._parent_id.hex(), self._trace_flags.hex()) 34 | if self._residue: 35 | retval += '-' + '-'.join(self._residue) 36 | return retval 37 | 38 | def get_version(self): 39 | return ord(self._version) 40 | 41 | def set_version(self, version): 42 | if isinstance(version, bytes): 43 | if len(version) != 1: 44 | raise ValueError('version must be a single byte') 45 | if version == b'\xff': 46 | raise ValueError('version 255 is now allowed') 47 | self._version = version 48 | elif isinstance(version, int): 49 | if version < 0 or version > 254: 50 | raise ValueError('version must be within range [0, 255)') 51 | self.set_version(bytes([version])) 52 | elif isinstance(version, str): 53 | if not self._VERSION_FORMAT_RE.match(version): 54 | raise ValueError('version {!r} does not match {}'.format(version, self._VERSION_FORMAT_RE)) 55 | self.set_version(bytes.fromhex(version)) 56 | else: 57 | raise ValueError('unsupported version type') 58 | 59 | def get_trace_id(self): 60 | return self._trace_id 61 | 62 | def set_trace_id(self, trace_id): 63 | if trace_id is None: 64 | self.set_trace_id(self._ZERO_TRACE_ID) 65 | elif isinstance(trace_id, bytes): 66 | if len(trace_id) != 16: 67 | raise ValueError('trace_id must contain 16 bytes') 68 | self._trace_id = trace_id 69 | elif isinstance(trace_id, str): 70 | if not self._TRACE_ID_FORMAT_RE.match(trace_id): 71 | raise ValueError('trace_id does not match {}'.format(self._TRACE_ID_FORMAT_RE)) 72 | self.set_trace_id(bytes.fromhex(trace_id)) 73 | else: 74 | raise ValueError('unsupported trace_id type') 75 | 76 | def get_parent_id(self): 77 | return self._parent_id 78 | 79 | def set_parent_id(self, parent_id): 80 | if parent_id is None: 81 | self.set_parent_id(self._ZERO_PARENT_ID) 82 | elif isinstance(parent_id, bytes): 83 | if len(parent_id) != 8: 84 | raise ValueError('parent_id must contain 8 bytes') 85 | self._parent_id = parent_id 86 | elif isinstance(parent_id, str): 87 | if not self._PARENT_ID_FORMAT_RE.match(parent_id): 88 | raise ValueError('parent_id does not match {}'.format(self._PARENT_ID_FORMAT_RE)) 89 | self.set_parent_id(bytes.fromhex(parent_id)) 90 | else: 91 | raise ValueError('unsupported parent_id type') 92 | 93 | def get_trace_flags(self): 94 | return ord(self._trace_flags) 95 | 96 | def set_trace_flags(self, trace_flags): 97 | if isinstance(trace_flags, bytes): 98 | if len(trace_flags) != 1: 99 | raise ValueError('trace_flags must be a single byte') 100 | self._trace_flags = trace_flags 101 | elif isinstance(trace_flags, int): 102 | if trace_flags < 0 or trace_flags > 255: 103 | raise ValueError('trace_flags must be within range [0, 255]') 104 | self.set_trace_flags(bytes([trace_flags])) 105 | elif isinstance(trace_flags, str): 106 | if not self._TRACE_FLAGS_FORMAT_RE.match(trace_flags): 107 | raise ValueError('trace_flags {!r} does not match {}'.format(trace_flags, self._TRACE_FLAGS_FORMAT_RE)) 108 | self.set_trace_flags(bytes.fromhex(trace_flags)) 109 | else: 110 | raise ValueError('unsupported trace_flags type') 111 | 112 | version = property(get_version, set_version) 113 | trace_id = property(get_trace_id, set_trace_id) 114 | parent_id = property(get_parent_id, set_parent_id) 115 | trace_flags = property(get_trace_flags, set_trace_flags) 116 | 117 | class Traceparent(BaseTraceparent): 118 | def __init__(self, version = 0, trace_id = None, parent_id = None, trace_flags = 0): 119 | if trace_id is None: 120 | trace_id = uuid.uuid1().hex 121 | if parent_id is None: 122 | parent_id = uuid.uuid4().hex[:16] 123 | super().__init__(version, trace_id, parent_id, trace_flags) 124 | 125 | def set_version(self, version): 126 | if version != 0 and version != b'\0' and version != '00': 127 | raise ValueError('unsupported version') 128 | super().set_version(version) 129 | 130 | def set_trace_id(self, trace_id): 131 | if trace_id == self._ZERO_TRACE_ID: 132 | raise ValueError('all zero trace_id is not allowed') 133 | super().set_trace_id(trace_id) 134 | 135 | def set_parent_id(self, parent_id): 136 | if parent_id == self._ZERO_PARENT_ID: 137 | raise ValueError('all zero parent_id is not allowed') 138 | super().set_parent_id(parent_id) 139 | 140 | def set_trace_flags(self, trace_flags): 141 | super().set_trace_flags(trace_flags) 142 | 143 | version = property(BaseTraceparent.get_version, set_version) 144 | trace_id = property(BaseTraceparent.get_trace_id, set_trace_id) 145 | parent_id = property(BaseTraceparent.get_parent_id, set_parent_id) 146 | trace_flags = property(BaseTraceparent.get_trace_flags, set_trace_flags) 147 | -------------------------------------------------------------------------------- /test/tracecontext/tracestate.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | import re 3 | 4 | class Tracestate(object): 5 | _KEY_WITHOUT_VENDOR_FORMAT = r'[a-z][_0-9a-z\-\*\/]{0,255}' 6 | _KEY_WITH_VENDOR_FORMAT = r'[0-9a-z][_0-9a-z\-\*\/]{0,240}@[a-z][_0-9a-z\-\*\/]{0,13}' 7 | _KEY_FORMAT = _KEY_WITHOUT_VENDOR_FORMAT + '|' + _KEY_WITH_VENDOR_FORMAT 8 | _VALUE_FORMAT = r'[\x20-\x2b\x2d-\x3c\x3e-\x7e]{0,255}[\x21-\x2b\x2d-\x3c\x3e-\x7e]' 9 | _DELIMITER_FORMAT_RE = re.compile('[ \t]*,[ \t]*') 10 | _KEY_VALIDATION_RE = re.compile('^(' + _KEY_FORMAT + ')$') 11 | _VALUE_VALIDATION_RE = re.compile('^(' + _VALUE_FORMAT + ')$') 12 | _MEMBER_FORMAT_RE = re.compile('^(%s)(=)(%s)$' % (_KEY_FORMAT, _VALUE_FORMAT)) 13 | 14 | def __init__(self, *args, **kwds): 15 | if len(args) == 1 and not kwds: 16 | if isinstance(args[0], str): 17 | self._traits = OrderedDict() 18 | self.from_string(args[0]) 19 | return 20 | if isinstance(args[0], Tracestate): 21 | self._traits = OrderedDict(args[0]._traits) 22 | return 23 | self._traits = OrderedDict(*args, **kwds) 24 | 25 | def __contains__(self, key): 26 | return key in self._traits 27 | 28 | def __len__(self): 29 | return len(self._traits) 30 | 31 | def __repr__(self): 32 | return '{}({!r})'.format(type(self).__name__, str(self)) 33 | 34 | def __getitem__(self, key): 35 | return self._traits[key] 36 | 37 | def __setitem__(self, key, value): 38 | if not isinstance(key, str): 39 | raise ValueError('key must be an instance of str') 40 | if not re.match(self._KEY_VALIDATION_RE, key): 41 | raise ValueError('illegal key provided') 42 | if not isinstance(value, str): 43 | raise ValueError('value must be an instance of str') 44 | if not re.match(self._VALUE_VALIDATION_RE, value): 45 | raise ValueError('illegal value provided') 46 | self._traits[key] = value 47 | self._traits.move_to_end(key, last = False) 48 | 49 | def __str__(self): 50 | return self.to_string() 51 | 52 | def from_string(self, string): 53 | for member in re.split(self._DELIMITER_FORMAT_RE, string): 54 | if member: 55 | match = self._MEMBER_FORMAT_RE.match(member) 56 | if not match: 57 | raise ValueError('illegal key-value format {!r}'.format(member)) 58 | key, eq, value = match.groups() 59 | if key not in self._traits: 60 | self._traits[key] = value 61 | # If key is already in self._traits, the incoming tracestate header contained a duplicated key. 62 | # According to the spec, two behaviors are valid: Either pass on the duplicated key as-is or drop 63 | # it. We opt for dropping it. 64 | return self 65 | 66 | def to_string(self): 67 | return ','.join(map(lambda key: key + '=' + self[key], self._traits)) 68 | 69 | # make this an optional choice instead of enforcement during put/update 70 | # if the tracestate value size is bigger than 512 characters, the tracer 71 | # CAN decide to forward the tracestate 72 | def is_valid(self): 73 | if len(self) == 0: 74 | return False 75 | # combined header length MUST be less than or equal to 512 bytes 76 | if len(self.to_string()) > 512: 77 | return False 78 | # there can be a maximum of 32 list-members in a list 79 | if len(self) > 32: 80 | return False 81 | return True 82 | 83 | def pop(self): 84 | return self._traits.popitem() 85 | -------------------------------------------------------------------------------- /w3c.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": "wg/distributed-tracing" 3 | , "contacts": "plehegar" 4 | , "policy": "open" 5 | , "repo-type": "rec-track" 6 | } 7 | --------------------------------------------------------------------------------