pax_global_header00006660000000000000000000000064137755341520014526gustar00rootroot0000000000000052 comment=b90a1d89871dad29dae770fdb19b42aaec4b9616 golang-github-opentracing-opentracing-go-1.2.0/000077500000000000000000000000001377553415200214765ustar00rootroot00000000000000golang-github-opentracing-opentracing-go-1.2.0/.github/000077500000000000000000000000001377553415200230365ustar00rootroot00000000000000golang-github-opentracing-opentracing-go-1.2.0/.github/ISSUE_TEMPLATE.md000066400000000000000000000016021377553415200255420ustar00rootroot00000000000000 ## Use Case Please explain your user story, what you are trying to do, which problem you are trying to solve. ## Problem What prevents you from solving your use case. ## Proposal A proposal that from your POV would solve the problem or improve the existing situation. If you don't have a proposed solution, that's fine too. ## Questions to address (if any) Questions that should be answered during the discussion of this issue before jumping into code. golang-github-opentracing-opentracing-go-1.2.0/.gitignore000066400000000000000000000000151377553415200234620ustar00rootroot00000000000000coverage.txt golang-github-opentracing-opentracing-go-1.2.0/.travis.yml000066400000000000000000000010221377553415200236020ustar00rootroot00000000000000language: go matrix: include: - go: "1.13.x" - go: "1.14.x" - go: "tip" env: - LINT=true - COVERAGE=true install: - if [ "$LINT" == true ]; then go get -u golang.org/x/lint/golint/... ; else echo 'skipping lint'; fi - go get -u github.com/stretchr/testify/... script: - make test - go build ./... - if [ "$LINT" == true ]; then make lint ; else echo 'skipping lint'; fi - if [ "$COVERAGE" == true ]; then make cover && bash <(curl -s https://codecov.io/bash) ; else echo 'skipping coverage'; fi golang-github-opentracing-opentracing-go-1.2.0/CHANGELOG.md000066400000000000000000000051241377553415200233110ustar00rootroot00000000000000Changes by Version ================== 1.2.0 (2020-07-01) ------------------- * Restore the ability to reset the current span in context to nil (#231) -- Yuri Shkuro * Use error.object per OpenTracing Semantic Conventions (#179) -- Rahman Syed * Convert nil pointer log field value to string "nil" (#230) -- Cyril Tovena * Add Go module support (#215) -- Zaba505 * Make SetTag helper types in ext public (#229) -- Blake Edwards * Add log/fields helpers for keys from specification (#226) -- Dmitry Monakhov * Improve noop impementation (#223) -- chanxuehong * Add an extension to Tracer interface for custom go context creation (#220) -- Krzesimir Nowak * Fix typo in comments (#222) -- meteorlxy * Improve documentation for log.Object() to emphasize the requirement to pass immutable arguments (#219) -- η–―η‹‚ηš„ε°δΌιΉ… * [mock] Return ErrInvalidSpanContext if span context is not MockSpanContext (#216) -- Milad Irannejad 1.1.0 (2019-03-23) ------------------- Notable changes: - The library is now released under Apache 2.0 license - Use Set() instead of Add() in HTTPHeadersCarrier is functionally a breaking change (fixes issue [#159](https://github.com/opentracing/opentracing-go/issues/159)) - 'golang.org/x/net/context' is replaced with 'context' from the standard library List of all changes: - Export StartSpanFromContextWithTracer (#214) - Add IsGlobalTracerRegistered() to indicate if a tracer has been registered (#201) - Use Set() instead of Add() in HTTPHeadersCarrier (#191) - Update license to Apache 2.0 (#181) - Replace 'golang.org/x/net/context' with 'context' (#176) - Port of Python opentracing/harness/api_check.py to Go (#146) - Fix race condition in MockSpan.Context() (#170) - Add PeerHostIPv4.SetString() (#155) - Add a Noop log field type to log to allow for optional fields (#150) 1.0.2 (2017-04-26) ------------------- - Add more semantic tags (#139) 1.0.1 (2017-02-06) ------------------- - Correct spelling in comments - Address race in nextMockID() (#123) - log: avoid panic marshaling nil error (#131) - Deprecate InitGlobalTracer in favor of SetGlobalTracer (#128) - Drop Go 1.5 that fails in Travis (#129) - Add convenience methods Key() and Value() to log.Field - Add convenience methods to log.Field (2 years, 6 months ago) 1.0.0 (2016-09-26) ------------------- - This release implements OpenTracing Specification 1.0 (https://opentracing.io/spec) golang-github-opentracing-opentracing-go-1.2.0/LICENSE000066400000000000000000000261311377553415200225060ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2016 The OpenTracing Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. golang-github-opentracing-opentracing-go-1.2.0/Makefile000066400000000000000000000006011377553415200231330ustar00rootroot00000000000000.DEFAULT_GOAL := test-and-lint .PHONY: test-and-lint test-and-lint: test lint .PHONY: test test: go test -v -cover -race ./... .PHONY: cover cover: go test -v -coverprofile=coverage.txt -covermode=atomic -race ./... .PHONY: lint lint: go fmt ./... golint ./... @# Run again with magic to exit non-zero if golint outputs anything. @! (golint ./... | read dummy) go vet ./... golang-github-opentracing-opentracing-go-1.2.0/README.md000066400000000000000000000131511377553415200227560ustar00rootroot00000000000000[![Gitter chat](http://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/opentracing/public) [![Build Status](https://travis-ci.org/opentracing/opentracing-go.svg?branch=master)](https://travis-ci.org/opentracing/opentracing-go) [![GoDoc](https://godoc.org/github.com/opentracing/opentracing-go?status.svg)](http://godoc.org/github.com/opentracing/opentracing-go) [![Sourcegraph Badge](https://sourcegraph.com/github.com/opentracing/opentracing-go/-/badge.svg)](https://sourcegraph.com/github.com/opentracing/opentracing-go?badge) # OpenTracing API for Go This package is a Go platform API for OpenTracing. ## Required Reading In order to understand the Go platform API, one must first be familiar with the [OpenTracing project](https://opentracing.io) and [terminology](https://opentracing.io/specification/) more specifically. ## API overview for those adding instrumentation Everyday consumers of this `opentracing` package really only need to worry about a couple of key abstractions: the `StartSpan` function, the `Span` interface, and binding a `Tracer` at `main()`-time. Here are code snippets demonstrating some important use cases. #### Singleton initialization The simplest starting point is `./default_tracer.go`. As early as possible, call ```go import "github.com/opentracing/opentracing-go" import ".../some_tracing_impl" func main() { opentracing.SetGlobalTracer( // tracing impl specific: some_tracing_impl.New(...), ) ... } ``` #### Non-Singleton initialization If you prefer direct control to singletons, manage ownership of the `opentracing.Tracer` implementation explicitly. #### Creating a Span given an existing Go `context.Context` If you use `context.Context` in your application, OpenTracing's Go library will happily rely on it for `Span` propagation. To start a new (blocking child) `Span`, you can use `StartSpanFromContext`. ```go func xyz(ctx context.Context, ...) { ... span, ctx := opentracing.StartSpanFromContext(ctx, "operation_name") defer span.Finish() span.LogFields( log.String("event", "soft error"), log.String("type", "cache timeout"), log.Int("waited.millis", 1500)) ... } ``` #### Starting an empty trace by creating a "root span" It's always possible to create a "root" `Span` with no parent or other causal reference. ```go func xyz() { ... sp := opentracing.StartSpan("operation_name") defer sp.Finish() ... } ``` #### Creating a (child) Span given an existing (parent) Span ```go func xyz(parentSpan opentracing.Span, ...) { ... sp := opentracing.StartSpan( "operation_name", opentracing.ChildOf(parentSpan.Context())) defer sp.Finish() ... } ``` #### Serializing to the wire ```go func makeSomeRequest(ctx context.Context) ... { if span := opentracing.SpanFromContext(ctx); span != nil { httpClient := &http.Client{} httpReq, _ := http.NewRequest("GET", "http://myservice/", nil) // Transmit the span's TraceContext as HTTP headers on our // outbound request. opentracing.GlobalTracer().Inject( span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(httpReq.Header)) resp, err := httpClient.Do(httpReq) ... } ... } ``` #### Deserializing from the wire ```go http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { var serverSpan opentracing.Span appSpecificOperationName := ... wireContext, err := opentracing.GlobalTracer().Extract( opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header)) if err != nil { // Optionally record something about err here } // Create the span referring to the RPC client if available. // If wireContext == nil, a root span will be created. serverSpan = opentracing.StartSpan( appSpecificOperationName, ext.RPCServerOption(wireContext)) defer serverSpan.Finish() ctx := opentracing.ContextWithSpan(context.Background(), serverSpan) ... } ``` #### Conditionally capture a field using `log.Noop` In some situations, you may want to dynamically decide whether or not to log a field. For example, you may want to capture additional data, such as a customer ID, in non-production environments: ```go func Customer(order *Order) log.Field { if os.Getenv("ENVIRONMENT") == "dev" { return log.String("customer", order.Customer.ID) } return log.Noop() } ``` #### Goroutine-safety The entire public API is goroutine-safe and does not require external synchronization. ## API pointers for those implementing a tracing system Tracing system implementors may be able to reuse or copy-paste-modify the `basictracer` package, found [here](https://github.com/opentracing/basictracer-go). In particular, see `basictracer.New(...)`. ## API compatibility For the time being, "mild" backwards-incompatible changes may be made without changing the major version number. As OpenTracing and `opentracing-go` mature, backwards compatibility will become more of a priority. ## Tracer test suite A test suite is available in the [harness](https://godoc.org/github.com/opentracing/opentracing-go/harness) package that can assist Tracer implementors to assert that their Tracer is working correctly. ## Licensing [Apache 2.0 License](./LICENSE). golang-github-opentracing-opentracing-go-1.2.0/ext.go000066400000000000000000000015461377553415200226330ustar00rootroot00000000000000package opentracing import ( "context" ) // TracerContextWithSpanExtension is an extension interface that the // implementation of the Tracer interface may want to implement. It // allows to have some control over the go context when the // ContextWithSpan is invoked. // // The primary purpose of this extension are adapters from opentracing // API to some other tracing API. type TracerContextWithSpanExtension interface { // ContextWithSpanHook gets called by the ContextWithSpan // function, when the Tracer implementation also implements // this interface. It allows to put extra information into the // context and make it available to the callers of the // ContextWithSpan. // // This hook is invoked before the ContextWithSpan function // actually puts the span into the context. ContextWithSpanHook(ctx context.Context, span Span) context.Context } golang-github-opentracing-opentracing-go-1.2.0/ext/000077500000000000000000000000001377553415200222765ustar00rootroot00000000000000golang-github-opentracing-opentracing-go-1.2.0/ext/field.go000066400000000000000000000006231377553415200237110ustar00rootroot00000000000000package ext import ( "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/log" ) // LogError sets the error=true tag on the Span and logs err as an "error" event. func LogError(span opentracing.Span, err error, fields ...log.Field) { Error.Set(span, true) ef := []log.Field{ log.Event("error"), log.Error(err), } ef = append(ef, fields...) span.LogFields(ef...) } golang-github-opentracing-opentracing-go-1.2.0/ext/field_test.go000066400000000000000000000021471377553415200247530ustar00rootroot00000000000000package ext_test import ( "fmt" "reflect" "testing" "github.com/stretchr/testify/assert" "github.com/opentracing/opentracing-go/ext" "github.com/opentracing/opentracing-go/log" "github.com/opentracing/opentracing-go/mocktracer" ) func TestLogError(t *testing.T) { tracer := mocktracer.New() span := tracer.StartSpan("my-trace") ext.Component.Set(span, "my-awesome-library") ext.SamplingPriority.Set(span, 1) err := fmt.Errorf("my error") ext.LogError(span, err, log.Message("my optional msg text")) span.Finish() rawSpan := tracer.FinishedSpans()[0] assert.Equal(t, map[string]interface{}{ "component": "my-awesome-library", "error": true, }, rawSpan.Tags()) assert.Equal(t, len(rawSpan.Logs()), 1) fields := rawSpan.Logs()[0].Fields assert.Equal(t, []mocktracer.MockKeyValue{ { Key: "event", ValueKind: reflect.String, ValueString: "error", }, { Key: "error.object", ValueKind: reflect.String, ValueString: err.Error(), }, { Key: "message", ValueKind: reflect.String, ValueString: "my optional msg text", }, }, fields) } golang-github-opentracing-opentracing-go-1.2.0/ext/tags.go000066400000000000000000000166311377553415200235720ustar00rootroot00000000000000package ext import "github.com/opentracing/opentracing-go" // These constants define common tag names recommended for better portability across // tracing systems and languages/platforms. // // The tag names are defined as typed strings, so that in addition to the usual use // // span.setTag(TagName, value) // // they also support value type validation via this additional syntax: // // TagName.Set(span, value) // var ( ////////////////////////////////////////////////////////////////////// // SpanKind (client/server or producer/consumer) ////////////////////////////////////////////////////////////////////// // SpanKind hints at relationship between spans, e.g. client/server SpanKind = spanKindTagName("span.kind") // SpanKindRPCClient marks a span representing the client-side of an RPC // or other remote call SpanKindRPCClientEnum = SpanKindEnum("client") SpanKindRPCClient = opentracing.Tag{Key: string(SpanKind), Value: SpanKindRPCClientEnum} // SpanKindRPCServer marks a span representing the server-side of an RPC // or other remote call SpanKindRPCServerEnum = SpanKindEnum("server") SpanKindRPCServer = opentracing.Tag{Key: string(SpanKind), Value: SpanKindRPCServerEnum} // SpanKindProducer marks a span representing the producer-side of a // message bus SpanKindProducerEnum = SpanKindEnum("producer") SpanKindProducer = opentracing.Tag{Key: string(SpanKind), Value: SpanKindProducerEnum} // SpanKindConsumer marks a span representing the consumer-side of a // message bus SpanKindConsumerEnum = SpanKindEnum("consumer") SpanKindConsumer = opentracing.Tag{Key: string(SpanKind), Value: SpanKindConsumerEnum} ////////////////////////////////////////////////////////////////////// // Component name ////////////////////////////////////////////////////////////////////// // Component is a low-cardinality identifier of the module, library, // or package that is generating a span. Component = StringTagName("component") ////////////////////////////////////////////////////////////////////// // Sampling hint ////////////////////////////////////////////////////////////////////// // SamplingPriority determines the priority of sampling this Span. SamplingPriority = Uint16TagName("sampling.priority") ////////////////////////////////////////////////////////////////////// // Peer tags. These tags can be emitted by either client-side or // server-side to describe the other side/service in a peer-to-peer // communications, like an RPC call. ////////////////////////////////////////////////////////////////////// // PeerService records the service name of the peer. PeerService = StringTagName("peer.service") // PeerAddress records the address name of the peer. This may be a "ip:port", // a bare "hostname", a FQDN or even a database DSN substring // like "mysql://username@127.0.0.1:3306/dbname" PeerAddress = StringTagName("peer.address") // PeerHostname records the host name of the peer PeerHostname = StringTagName("peer.hostname") // PeerHostIPv4 records IP v4 host address of the peer PeerHostIPv4 = IPv4TagName("peer.ipv4") // PeerHostIPv6 records IP v6 host address of the peer PeerHostIPv6 = StringTagName("peer.ipv6") // PeerPort records port number of the peer PeerPort = Uint16TagName("peer.port") ////////////////////////////////////////////////////////////////////// // HTTP Tags ////////////////////////////////////////////////////////////////////// // HTTPUrl should be the URL of the request being handled in this segment // of the trace, in standard URI format. The protocol is optional. HTTPUrl = StringTagName("http.url") // HTTPMethod is the HTTP method of the request, and is case-insensitive. HTTPMethod = StringTagName("http.method") // HTTPStatusCode is the numeric HTTP status code (200, 404, etc) of the // HTTP response. HTTPStatusCode = Uint16TagName("http.status_code") ////////////////////////////////////////////////////////////////////// // DB Tags ////////////////////////////////////////////////////////////////////// // DBInstance is database instance name. DBInstance = StringTagName("db.instance") // DBStatement is a database statement for the given database type. // It can be a query or a prepared statement (i.e., before substitution). DBStatement = StringTagName("db.statement") // DBType is a database type. For any SQL database, "sql". // For others, the lower-case database category, e.g. "redis" DBType = StringTagName("db.type") // DBUser is a username for accessing database. DBUser = StringTagName("db.user") ////////////////////////////////////////////////////////////////////// // Message Bus Tag ////////////////////////////////////////////////////////////////////// // MessageBusDestination is an address at which messages can be exchanged MessageBusDestination = StringTagName("message_bus.destination") ////////////////////////////////////////////////////////////////////// // Error Tag ////////////////////////////////////////////////////////////////////// // Error indicates that operation represented by the span resulted in an error. Error = BoolTagName("error") ) // --- // SpanKindEnum represents common span types type SpanKindEnum string type spanKindTagName string // Set adds a string tag to the `span` func (tag spanKindTagName) Set(span opentracing.Span, value SpanKindEnum) { span.SetTag(string(tag), value) } type rpcServerOption struct { clientContext opentracing.SpanContext } func (r rpcServerOption) Apply(o *opentracing.StartSpanOptions) { if r.clientContext != nil { opentracing.ChildOf(r.clientContext).Apply(o) } SpanKindRPCServer.Apply(o) } // RPCServerOption returns a StartSpanOption appropriate for an RPC server span // with `client` representing the metadata for the remote peer Span if available. // In case client == nil, due to the client not being instrumented, this RPC // server span will be a root span. func RPCServerOption(client opentracing.SpanContext) opentracing.StartSpanOption { return rpcServerOption{client} } // --- // StringTagName is a common tag name to be set to a string value type StringTagName string // Set adds a string tag to the `span` func (tag StringTagName) Set(span opentracing.Span, value string) { span.SetTag(string(tag), value) } // --- // Uint32TagName is a common tag name to be set to a uint32 value type Uint32TagName string // Set adds a uint32 tag to the `span` func (tag Uint32TagName) Set(span opentracing.Span, value uint32) { span.SetTag(string(tag), value) } // --- // Uint16TagName is a common tag name to be set to a uint16 value type Uint16TagName string // Set adds a uint16 tag to the `span` func (tag Uint16TagName) Set(span opentracing.Span, value uint16) { span.SetTag(string(tag), value) } // --- // BoolTagName is a common tag name to be set to a bool value type BoolTagName string // Set adds a bool tag to the `span` func (tag BoolTagName) Set(span opentracing.Span, value bool) { span.SetTag(string(tag), value) } // IPv4TagName is a common tag name to be set to an ipv4 value type IPv4TagName string // Set adds IP v4 host address of the peer as an uint32 value to the `span`, keep this for backward and zipkin compatibility func (tag IPv4TagName) Set(span opentracing.Span, value uint32) { span.SetTag(string(tag), value) } // SetString records IP v4 host address of the peer as a .-separated tuple to the `span`. E.g., "127.0.0.1" func (tag IPv4TagName) SetString(span opentracing.Span, value string) { span.SetTag(string(tag), value) } golang-github-opentracing-opentracing-go-1.2.0/ext/tags_test.go000066400000000000000000000104661377553415200246310ustar00rootroot00000000000000package ext_test import ( "testing" "github.com/stretchr/testify/assert" "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" "github.com/opentracing/opentracing-go/mocktracer" ) func TestPeerTags(t *testing.T) { if ext.PeerService != "peer.service" { t.Fatalf("Invalid PeerService %v", ext.PeerService) } tracer := mocktracer.New() span := tracer.StartSpan("my-trace") ext.PeerService.Set(span, "my-service") ext.PeerAddress.Set(span, "my-hostname:8080") ext.PeerHostname.Set(span, "my-hostname") ext.PeerHostIPv4.Set(span, uint32(127<<24|1)) ext.PeerHostIPv6.Set(span, "::") ext.PeerPort.Set(span, uint16(8080)) ext.SamplingPriority.Set(span, uint16(1)) ext.SpanKind.Set(span, ext.SpanKindRPCServerEnum) ext.SpanKindRPCClient.Set(span) span.Finish() rawSpan := tracer.FinishedSpans()[0] assert.Equal(t, map[string]interface{}{ "peer.service": "my-service", "peer.address": "my-hostname:8080", "peer.hostname": "my-hostname", "peer.ipv4": uint32(127<<24 | 1), "peer.ipv6": "::", "peer.port": uint16(8080), "span.kind": ext.SpanKindRPCClientEnum, }, rawSpan.Tags()) assert.True(t, span.Context().(mocktracer.MockSpanContext).Sampled) ext.SamplingPriority.Set(span, uint16(0)) assert.False(t, span.Context().(mocktracer.MockSpanContext).Sampled) } func TestHTTPTags(t *testing.T) { tracer := mocktracer.New() span := tracer.StartSpan("my-trace", ext.SpanKindRPCServer) ext.HTTPUrl.Set(span, "test.biz/uri?protocol=false") ext.HTTPMethod.Set(span, "GET") ext.HTTPStatusCode.Set(span, 301) span.Finish() rawSpan := tracer.FinishedSpans()[0] assert.Equal(t, map[string]interface{}{ "http.url": "test.biz/uri?protocol=false", "http.method": "GET", "http.status_code": uint16(301), "span.kind": ext.SpanKindRPCServerEnum, }, rawSpan.Tags()) } func TestDBTags(t *testing.T) { tracer := mocktracer.New() span := tracer.StartSpan("my-trace", ext.SpanKindRPCClient) ext.DBInstance.Set(span, "127.0.0.1:3306/customers") ext.DBStatement.Set(span, "SELECT * FROM user_table") ext.DBType.Set(span, "sql") ext.DBUser.Set(span, "customer_user") span.Finish() rawSpan := tracer.FinishedSpans()[0] assert.Equal(t, map[string]interface{}{ "db.instance": "127.0.0.1:3306/customers", "db.statement": "SELECT * FROM user_table", "db.type": "sql", "db.user": "customer_user", "span.kind": ext.SpanKindRPCClientEnum, }, rawSpan.Tags()) } func TestMiscTags(t *testing.T) { tracer := mocktracer.New() span := tracer.StartSpan("my-trace") ext.Component.Set(span, "my-awesome-library") ext.SamplingPriority.Set(span, 1) ext.Error.Set(span, true) span.Finish() rawSpan := tracer.FinishedSpans()[0] assert.Equal(t, map[string]interface{}{ "component": "my-awesome-library", "error": true, }, rawSpan.Tags()) } func TestRPCServerOption(t *testing.T) { tracer := mocktracer.New() parent := tracer.StartSpan("my-trace") parent.SetBaggageItem("bag", "gage") carrier := opentracing.HTTPHeadersCarrier{} err := tracer.Inject(parent.Context(), opentracing.HTTPHeaders, carrier) if err != nil { t.Fatal(err) } parCtx, err := tracer.Extract(opentracing.HTTPHeaders, carrier) if err != nil { t.Fatal(err) } tracer.StartSpan("my-child", ext.RPCServerOption(parCtx)).Finish() rawSpan := tracer.FinishedSpans()[0] assert.Equal(t, map[string]interface{}{ "span.kind": ext.SpanKindRPCServerEnum, }, rawSpan.Tags()) assert.Equal(t, map[string]string{ "bag": "gage", }, rawSpan.Context().(mocktracer.MockSpanContext).Baggage) } func TestMessageBusProducerTags(t *testing.T) { tracer := mocktracer.New() span := tracer.StartSpan("my-trace", ext.SpanKindProducer) ext.MessageBusDestination.Set(span, "topic name") span.Finish() rawSpan := tracer.FinishedSpans()[0] assert.Equal(t, map[string]interface{}{ "message_bus.destination": "topic name", "span.kind": ext.SpanKindProducerEnum, }, rawSpan.Tags()) } func TestMessageBusConsumerTags(t *testing.T) { tracer := mocktracer.New() span := tracer.StartSpan("my-trace", ext.SpanKindConsumer) ext.MessageBusDestination.Set(span, "topic name") span.Finish() rawSpan := tracer.FinishedSpans()[0] assert.Equal(t, map[string]interface{}{ "message_bus.destination": "topic name", "span.kind": ext.SpanKindConsumerEnum, }, rawSpan.Tags()) } golang-github-opentracing-opentracing-go-1.2.0/globaltracer.go000066400000000000000000000025731377553415200244750ustar00rootroot00000000000000package opentracing type registeredTracer struct { tracer Tracer isRegistered bool } var ( globalTracer = registeredTracer{NoopTracer{}, false} ) // SetGlobalTracer sets the [singleton] opentracing.Tracer returned by // GlobalTracer(). Those who use GlobalTracer (rather than directly manage an // opentracing.Tracer instance) should call SetGlobalTracer as early as // possible in main(), prior to calling the `StartSpan` global func below. // Prior to calling `SetGlobalTracer`, any Spans started via the `StartSpan` // (etc) globals are noops. func SetGlobalTracer(tracer Tracer) { globalTracer = registeredTracer{tracer, true} } // GlobalTracer returns the global singleton `Tracer` implementation. // Before `SetGlobalTracer()` is called, the `GlobalTracer()` is a noop // implementation that drops all data handed to it. func GlobalTracer() Tracer { return globalTracer.tracer } // StartSpan defers to `Tracer.StartSpan`. See `GlobalTracer()`. func StartSpan(operationName string, opts ...StartSpanOption) Span { return globalTracer.tracer.StartSpan(operationName, opts...) } // InitGlobalTracer is deprecated. Please use SetGlobalTracer. func InitGlobalTracer(tracer Tracer) { SetGlobalTracer(tracer) } // IsGlobalTracerRegistered returns a `bool` to indicate if a tracer has been globally registered func IsGlobalTracerRegistered() bool { return globalTracer.isRegistered } golang-github-opentracing-opentracing-go-1.2.0/globaltracer_test.go000066400000000000000000000012131377553415200255220ustar00rootroot00000000000000package opentracing import ( "reflect" "testing" ) func TestIsGlobalTracerRegisteredDefaultIsFalse(t *testing.T) { if IsGlobalTracerRegistered() { t.Errorf("Should return false when no global tracer is registered.") } } func TestAfterSettingGlobalTracerIsGlobalTracerRegisteredReturnsTrue(t *testing.T) { SetGlobalTracer(NoopTracer{}) if !IsGlobalTracerRegistered() { t.Errorf("Should return true after a tracer has been registered.") } } func TestDefaultTracerIsNoopTracer(t *testing.T) { if reflect.TypeOf(GlobalTracer()) != reflect.TypeOf(NoopTracer{}) { t.Errorf("Should return false when no global tracer is registered.") } } golang-github-opentracing-opentracing-go-1.2.0/go.mod000066400000000000000000000001421377553415200226010ustar00rootroot00000000000000module github.com/opentracing/opentracing-go go 1.14 require github.com/stretchr/testify v1.3.0 golang-github-opentracing-opentracing-go-1.2.0/go.sum000066400000000000000000000011401377553415200226250ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang-github-opentracing-opentracing-go-1.2.0/gocontext.go000066400000000000000000000046431377553415200240460ustar00rootroot00000000000000package opentracing import "context" type contextKey struct{} var activeSpanKey = contextKey{} // ContextWithSpan returns a new `context.Context` that holds a reference to // the span. If span is nil, a new context without an active span is returned. func ContextWithSpan(ctx context.Context, span Span) context.Context { if span != nil { if tracerWithHook, ok := span.Tracer().(TracerContextWithSpanExtension); ok { ctx = tracerWithHook.ContextWithSpanHook(ctx, span) } } return context.WithValue(ctx, activeSpanKey, span) } // SpanFromContext returns the `Span` previously associated with `ctx`, or // `nil` if no such `Span` could be found. // // NOTE: context.Context != SpanContext: the former is Go's intra-process // context propagation mechanism, and the latter houses OpenTracing's per-Span // identity and baggage information. func SpanFromContext(ctx context.Context) Span { val := ctx.Value(activeSpanKey) if sp, ok := val.(Span); ok { return sp } return nil } // StartSpanFromContext starts and returns a Span with `operationName`, using // any Span found within `ctx` as a ChildOfRef. If no such parent could be // found, StartSpanFromContext creates a root (parentless) Span. // // The second return value is a context.Context object built around the // returned Span. // // Example usage: // // SomeFunction(ctx context.Context, ...) { // sp, ctx := opentracing.StartSpanFromContext(ctx, "SomeFunction") // defer sp.Finish() // ... // } func StartSpanFromContext(ctx context.Context, operationName string, opts ...StartSpanOption) (Span, context.Context) { return StartSpanFromContextWithTracer(ctx, GlobalTracer(), operationName, opts...) } // StartSpanFromContextWithTracer starts and returns a span with `operationName` // using a span found within the context as a ChildOfRef. If that doesn't exist // it creates a root span. It also returns a context.Context object built // around the returned span. // // It's behavior is identical to StartSpanFromContext except that it takes an explicit // tracer as opposed to using the global tracer. func StartSpanFromContextWithTracer(ctx context.Context, tracer Tracer, operationName string, opts ...StartSpanOption) (Span, context.Context) { if parentSpan := SpanFromContext(ctx); parentSpan != nil { opts = append(opts, ChildOf(parentSpan.Context())) } span := tracer.StartSpan(operationName, opts...) return span, ContextWithSpan(ctx, span) } golang-github-opentracing-opentracing-go-1.2.0/gocontext_test.go000066400000000000000000000066421377553415200251060ustar00rootroot00000000000000package opentracing import ( "context" "testing" "time" "github.com/stretchr/testify/assert" ) func TestContextWithSpan(t *testing.T) { span := &noopSpan{} ctx := ContextWithSpan(context.Background(), span) span2 := SpanFromContext(ctx) if span != span2 { t.Errorf("Not the same span returned from context, expected=%+v, actual=%+v", span, span2) } ctx = context.Background() span2 = SpanFromContext(ctx) if span2 != nil { t.Errorf("Expected nil span, found %+v", span2) } ctx = ContextWithSpan(ctx, span) span2 = SpanFromContext(ctx) if span != span2 { t.Errorf("Not the same span returned from context, expected=%+v, actual=%+v", span, span2) } ctx = ContextWithSpan(ctx, nil) if s := SpanFromContext(ctx); s != nil { t.Errorf("Not able to reset span in context, expected=nil, actual=%+v", s) } } type noopExtTracer struct { NoopTracer } type noopExtTracerCtxType struct{} func (noopExtTracer) ContextWithSpanHook(ctx context.Context, span Span) context.Context { return context.WithValue(ctx, noopExtTracerCtxType{}, noopExtTracerCtxType{}) } var _ Tracer = noopExtTracer{} var _ TracerContextWithSpanExtension = noopExtTracer{} type noopExtSpan struct { noopSpan } func (noopExtSpan) Tracer() Tracer { return noopExtTracer{} } var _ Span = noopExtSpan{} func TestContextWithSpanWithExtension(t *testing.T) { span := &noopExtSpan{} ctx := ContextWithSpan(context.Background(), span) span2 := SpanFromContext(ctx) if span != span2 { t.Errorf("Not the same span returned from context, expected=%+v, actual=%+v", span, span2) } if _, ok := ctx.Value(noopExtTracerCtxType{}).(noopExtTracerCtxType); !ok { t.Error("ContextWithSpanHook was not called") } } func TestStartSpanFromContext(t *testing.T) { testTracer := testTracer{} // Test the case where there *is* a Span in the Context. { parentSpan := &testSpan{} parentCtx := ContextWithSpan(context.Background(), parentSpan) childSpan, childCtx := StartSpanFromContextWithTracer(parentCtx, testTracer, "child") if !childSpan.Context().(testSpanContext).HasParent { t.Errorf("Failed to find parent: %v", childSpan) } if !childSpan.(testSpan).Equal(SpanFromContext(childCtx)) { t.Errorf("Unable to find child span in context: %v", childCtx) } } // Test the case where there *is not* a Span in the Context. { emptyCtx := context.Background() childSpan, childCtx := StartSpanFromContextWithTracer(emptyCtx, testTracer, "child") if childSpan.Context().(testSpanContext).HasParent { t.Errorf("Should not have found parent: %v", childSpan) } if !childSpan.(testSpan).Equal(SpanFromContext(childCtx)) { t.Errorf("Unable to find child span in context: %v", childCtx) } } } func TestStartSpanFromContextOptions(t *testing.T) { testTracer := testTracer{} // Test options are passed to tracer startTime := time.Now().Add(-10 * time.Second) // ten seconds ago span, ctx := StartSpanFromContextWithTracer( context.Background(), testTracer, "parent", StartTime(startTime), Tag{"component", "test"}) assert.Equal(t, "test", span.(testSpan).Tags["component"]) assert.Equal(t, startTime, span.(testSpan).StartTime) // Test it also works for a child span childStartTime := startTime.Add(3 * time.Second) childSpan, _ := StartSpanFromContextWithTracer( ctx, testTracer, "child", StartTime(childStartTime)) assert.Equal(t, childSpan.(testSpan).Tags["component"], nil) assert.Equal(t, childSpan.(testSpan).StartTime, childStartTime) } golang-github-opentracing-opentracing-go-1.2.0/harness/000077500000000000000000000000001377553415200231415ustar00rootroot00000000000000golang-github-opentracing-opentracing-go-1.2.0/harness/api_checkers.go000066400000000000000000000406431377553415200261170ustar00rootroot00000000000000/* Package harness provides a suite of API compatibility checks. They were originally ported from the OpenTracing Python library's "harness" module. To run this test suite against your tracer, call harness.RunAPIChecks and provide it a function that returns a Tracer implementation and a function to call to close it. The function will be called to create a new tracer before each test in the suite is run, and the returned closer function will be called after each test is finished. Several options provide additional checks for your Tracer's behavior: CheckBaggageValues(true) indicates your tracer supports baggage propagation, CheckExtract(true) tells the suite to test if the Tracer can extract a trace context from text and binary carriers, and CheckInject(true) tests if the Tracer can inject the trace context into a carrier. The UseProbe option provides an APICheckProbe implementation that allows the test suite to additionally check if two Spans are part of the same trace, and if a Span and a SpanContext are part of the same trace. Implementing an APICheckProbe provides additional assertions that your tracer is working properly. */ package harness import ( "bytes" "testing" "time" "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) // APICheckCapabilities describes capabilities of a Tracer that should be checked by APICheckSuite. type APICheckCapabilities struct { CheckBaggageValues bool // whether to check for propagation of baggage values CheckExtract bool // whether to check if extracting contexts from carriers works CheckInject bool // whether to check if injecting contexts works Probe APICheckProbe // optional interface providing methods to check recorded data } // APICheckProbe exposes methods for testing data recorded by a Tracer. type APICheckProbe interface { // SameTrace helps tests assert that this tracer's spans are from the same trace. SameTrace(first, second opentracing.Span) bool // SameSpanContext helps tests assert that a span and a context are from the same trace and span. SameSpanContext(opentracing.Span, opentracing.SpanContext) bool } // APICheckSuite is a testify suite for checking a Tracer against the OpenTracing API. type APICheckSuite struct { suite.Suite opts APICheckCapabilities newTracer func() (tracer opentracing.Tracer, closer func()) tracer opentracing.Tracer closer func() } // RunAPIChecks runs a test suite to check a Tracer against the OpenTracing API. // It is provided a function that will be executed to create and destroy a tracer for each test // in the suite, and the given APICheckOption functional options `opts`. func RunAPIChecks( t *testing.T, newTracer func() (tracer opentracing.Tracer, closer func()), opts ...APICheckOption, ) { s := &APICheckSuite{newTracer: newTracer} for _, opt := range opts { opt(s) } suite.Run(t, s) } // APICheckOption instances may be passed to NewAPICheckSuite. type APICheckOption func(*APICheckSuite) // CheckBaggageValues returns an option that sets whether to check for propagation of baggage values. func CheckBaggageValues(val bool) APICheckOption { return func(s *APICheckSuite) { s.opts.CheckBaggageValues = val } } // CheckExtract returns an option that sets whether to check if extracting contexts from carriers works. func CheckExtract(val bool) APICheckOption { return func(s *APICheckSuite) { s.opts.CheckExtract = val } } // CheckInject returns an option that sets whether to check if injecting contexts works. func CheckInject(val bool) APICheckOption { return func(s *APICheckSuite) { s.opts.CheckInject = val } } // CheckEverything returns an option that enables all API checks. func CheckEverything() APICheckOption { return func(s *APICheckSuite) { s.opts.CheckBaggageValues = true s.opts.CheckExtract = true s.opts.CheckInject = true } } // UseProbe returns an option that specifies an APICheckProbe implementation to use. func UseProbe(probe APICheckProbe) APICheckOption { return func(s *APICheckSuite) { s.opts.Probe = probe } } // SetupTest creates a tracer for this specific test invocation. func (s *APICheckSuite) SetupTest() { s.tracer, s.closer = s.newTracer() if s.tracer == nil { s.T().Fatalf("newTracer returned nil Tracer") } } // TearDownTest closes the tracer, and clears the test-specific tracer. func (s *APICheckSuite) TearDownTest() { if s.closer != nil { s.closer() } s.tracer, s.closer = nil, nil } // TestStartSpan checks if a Tracer can start a span and calls some span API methods. func (s *APICheckSuite) TestStartSpan() { span := s.tracer.StartSpan( "Fry", opentracing.Tag{Key: "birthday", Value: "August 14 1974"}) span.LogFields( log.String("hospital", "Brooklyn Pre-Med Hospital"), log.String("city", "Old New York")) span.Finish() } // TestStartSpanWithParent checks if a Tracer can start a span with a specified parent. func (s *APICheckSuite) TestStartSpanWithParent() { parentSpan := s.tracer.StartSpan("Turanga Munda") s.NotNil(parentSpan) childFns := []func(opentracing.SpanContext) opentracing.SpanReference{ opentracing.ChildOf, opentracing.FollowsFrom, } for _, childFn := range childFns { span := s.tracer.StartSpan( "Leela", childFn(parentSpan.Context()), opentracing.Tag{Key: "birthplace", Value: "sewers"}) span.Finish() if s.opts.Probe != nil { s.True(s.opts.Probe.SameTrace(parentSpan, span)) } else { s.T().Log("harness.Probe not specified, skipping") } } parentSpan.Finish() } // TestSetOperationName attempts to set the operation name on a span after it has been created. func (s *APICheckSuite) TestSetOperationName() { span := s.tracer.StartSpan("").SetOperationName("Farnsworth") span.Finish() } // TestSpanTagValueTypes sets tags using values of different types. func (s *APICheckSuite) TestSpanTagValueTypes() { span := s.tracer.StartSpan("ManyTypes") span. SetTag("an_int", 9). SetTag("a_bool", true). SetTag("a_string", "aoeuidhtns") } // TestSpanTagsWithChaining tests chaining of calls to SetTag. func (s *APICheckSuite) TestSpanTagsWithChaining() { span := s.tracer.StartSpan("Farnsworth") span. SetTag("birthday", "9 April, 2841"). SetTag("loves", "different lengths of wires") span. SetTag("unicode_val", "non-ascii: \u200b"). SetTag("unicode_key_\u200b", "ascii val") span.Finish() } // TestSpanLogs tests calls to log keys and values with spans. func (s *APICheckSuite) TestSpanLogs() { span := s.tracer.StartSpan("Fry") span.LogKV( "event", "frozen", "year", 1999, "place", "Cryogenics Labs") span.LogKV( "event", "defrosted", "year", 2999, "place", "Cryogenics Labs") ts := time.Now() span.FinishWithOptions(opentracing.FinishOptions{ LogRecords: []opentracing.LogRecord{ { Timestamp: ts, Fields: []log.Field{ log.String("event", "job-assignment"), log.String("type", "delivery boy"), }, }, }}) // Test deprecated log methods span.LogEvent("an arbitrary event") span.LogEventWithPayload("y", "z") span.Log(opentracing.LogData{Event: "y", Payload: "z"}) } func assertEmptyBaggage(t *testing.T, spanContext opentracing.SpanContext) { if !assert.NotNil(t, spanContext, "assertEmptyBaggage got empty context") { return } spanContext.ForeachBaggageItem(func(k, v string) bool { assert.Fail(t, "new span shouldn't have baggage") return false }) } // TestSpanBaggage tests calls to set and get span baggage, and if the CheckBaggageValues option // is set, asserts that baggage values were successfully retrieved. func (s *APICheckSuite) TestSpanBaggage() { span := s.tracer.StartSpan("Fry") assertEmptyBaggage(s.T(), span.Context()) spanRef := span.SetBaggageItem("Kiff-loves", "Amy") s.Exactly(spanRef, span) val := span.BaggageItem("Kiff-loves") if s.opts.CheckBaggageValues { s.Equal("Amy", val) } else { s.T().Log("CheckBaggageValues capability not set, skipping") } span.Finish() } // TestContextBaggage tests calls to set and get span baggage, and if the CheckBaggageValues option // is set, asserts that baggage values were successfully retrieved from the span's SpanContext. func (s *APICheckSuite) TestContextBaggage() { span := s.tracer.StartSpan("Fry") assertEmptyBaggage(s.T(), span.Context()) span.SetBaggageItem("Kiff-loves", "Amy") if s.opts.CheckBaggageValues { called := false span.Context().ForeachBaggageItem(func(k, v string) bool { s.False(called) called = true s.Equal("Kiff-loves", k) s.Equal("Amy", v) return true }) } else { s.T().Log("CheckBaggageValues capability not set, skipping") } span.Finish() } // TestTextPropagation tests if the Tracer can Inject a span into a TextMapCarrier, and later Extract it. // If CheckExtract is set, it will check if Extract was successful (returned no error). If a Probe is set, // it will check if the extracted context is in the same trace as the original span. func (s *APICheckSuite) TestTextPropagation() { span := s.tracer.StartSpan("Bender") textCarrier := opentracing.TextMapCarrier{} err := span.Tracer().Inject(span.Context(), opentracing.TextMap, textCarrier) assert.NoError(s.T(), err) extractedContext, err := s.tracer.Extract(opentracing.TextMap, textCarrier) if s.opts.CheckExtract { s.NoError(err) assertEmptyBaggage(s.T(), extractedContext) } else { s.T().Log("CheckExtract capability not set, skipping") } if s.opts.Probe != nil { s.True(s.opts.Probe.SameSpanContext(span, extractedContext)) } else { s.T().Log("harness.Probe not specified, skipping") } span.Finish() } // TestHTTPPropagation tests if the Tracer can Inject a span into HTTP headers, and later Extract it. // If CheckExtract is set, it will check if Extract was successful (returned no error). If a Probe is set, // it will check if the extracted context is in the same trace as the original span. func (s *APICheckSuite) TestHTTPPropagation() { span := s.tracer.StartSpan("Bender") textCarrier := opentracing.HTTPHeadersCarrier{} err := span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, textCarrier) s.NoError(err) extractedContext, err := s.tracer.Extract(opentracing.HTTPHeaders, textCarrier) if s.opts.CheckExtract { s.NoError(err) assertEmptyBaggage(s.T(), extractedContext) } else { s.T().Log("CheckExtract capability not set, skipping") } if s.opts.Probe != nil { s.True(s.opts.Probe.SameSpanContext(span, extractedContext)) } else { s.T().Log("harness.Probe not specified, skipping") } span.Finish() } // TestBinaryPropagation tests if the Tracer can Inject a span into a binary buffer, and later Extract it. // If CheckExtract is set, it will check if Extract was successful (returned no error). If a Probe is set, // it will check if the extracted context is in the same trace as the original span. func (s *APICheckSuite) TestBinaryPropagation() { span := s.tracer.StartSpan("Bender") buf := new(bytes.Buffer) err := span.Tracer().Inject(span.Context(), opentracing.Binary, buf) s.NoError(err) extractedContext, err := s.tracer.Extract(opentracing.Binary, buf) if s.opts.CheckExtract { s.NoError(err) assertEmptyBaggage(s.T(), extractedContext) } else { s.T().Log("CheckExtract capability not set, skipping") } if s.opts.Probe != nil { s.True(s.opts.Probe.SameSpanContext(span, extractedContext)) } else { s.T().Log("harness.Probe not specified, skipping") } span.Finish() } // TestMandatoryFormats tests if all mandatory carrier formats are supported. If CheckExtract is set, it // will check if the call to Extract was successful (returned no error such as ErrUnsupportedFormat). func (s *APICheckSuite) TestMandatoryFormats() { formats := []struct{ Format, Carrier interface{} }{ {opentracing.TextMap, opentracing.TextMapCarrier{}}, {opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier{}}, {opentracing.Binary, new(bytes.Buffer)}, } span := s.tracer.StartSpan("Bender") for _, fmtCarrier := range formats { err := span.Tracer().Inject(span.Context(), fmtCarrier.Format, fmtCarrier.Carrier) s.NoError(err) spanCtx, err := s.tracer.Extract(fmtCarrier.Format, fmtCarrier.Carrier) if s.opts.CheckExtract { s.NoError(err) assertEmptyBaggage(s.T(), spanCtx) } else { s.T().Log("CheckExtract capability not set, skipping") } } } // TestUnknownFormat checks if attempting to Inject or Extract using an unsupported format // returns ErrUnsupportedFormat, if CheckInject and CheckExtract are set. func (s *APICheckSuite) TestUnknownFormat() { customFormat := "kiss my shiny metal ..." span := s.tracer.StartSpan("Bender") err := span.Tracer().Inject(span.Context(), customFormat, nil) if s.opts.CheckInject { s.Equal(opentracing.ErrUnsupportedFormat, err) } else { s.T().Log("CheckInject capability not set, skipping") } ctx, err := s.tracer.Extract(customFormat, nil) s.Nil(ctx) if s.opts.CheckExtract { s.Equal(opentracing.ErrUnsupportedFormat, err) } else { s.T().Log("CheckExtract capability not set, skipping") } } // ForeignSpanContext satisfies the opentracing.SpanContext interface, but otherwise does nothing. type ForeignSpanContext struct{} // ForeachBaggageItem could call handler for each baggage KV, but does nothing. func (f ForeignSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {} // NotACarrier does not satisfy any of the opentracing carrier interfaces. type NotACarrier struct{} // TestInvalidInject checks if errors are returned when Inject is called with invalid inputs. func (s *APICheckSuite) TestInvalidInject() { if !s.opts.CheckInject { s.T().Skip("CheckInject capability not set, skipping") } span := s.tracer.StartSpan("op") // binary inject err := span.Tracer().Inject(ForeignSpanContext{}, opentracing.Binary, new(bytes.Buffer)) s.Equal(opentracing.ErrInvalidSpanContext, err, "Foreign SpanContext should return invalid error") err = span.Tracer().Inject(span.Context(), opentracing.Binary, NotACarrier{}) s.Equal(opentracing.ErrInvalidCarrier, err, "Carrier that's not io.Writer should return error") // text inject err = span.Tracer().Inject(ForeignSpanContext{}, opentracing.TextMap, opentracing.TextMapCarrier{}) s.Equal(opentracing.ErrInvalidSpanContext, err, "Foreign SpanContext should return invalid error") err = span.Tracer().Inject(span.Context(), opentracing.TextMap, NotACarrier{}) s.Equal(opentracing.ErrInvalidCarrier, err, "Carrier that's not TextMapWriter should return error") // HTTP inject err = span.Tracer().Inject(ForeignSpanContext{}, opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier{}) s.Equal(opentracing.ErrInvalidSpanContext, err, "Foreign SpanContext should return invalid error") err = span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, NotACarrier{}) s.Equal(opentracing.ErrInvalidCarrier, err, "Carrier that's not TextMapWriter should return error") } // TestInvalidExtract checks if errors are returned when Extract is called with invalid inputs. func (s *APICheckSuite) TestInvalidExtract() { if !s.opts.CheckExtract { s.T().Skip("CheckExtract capability not set, skipping") } span := s.tracer.StartSpan("op") // binary extract ctx, err := span.Tracer().Extract(opentracing.Binary, NotACarrier{}) s.Equal(opentracing.ErrInvalidCarrier, err, "Carrier that's not io.Reader should return error") s.Nil(ctx) // text extract ctx, err = span.Tracer().Extract(opentracing.TextMap, NotACarrier{}) s.Equal(opentracing.ErrInvalidCarrier, err, "Carrier that's not TextMapReader should return error") s.Nil(ctx) // HTTP extract ctx, err = span.Tracer().Extract(opentracing.HTTPHeaders, NotACarrier{}) s.Equal(opentracing.ErrInvalidCarrier, err, "Carrier that's not TextMapReader should return error") s.Nil(ctx) span.Finish() } // TestMultiBaggage tests calls to set multiple baggage items, and if the CheckBaggageValues option // is set, asserts that a baggage value was successfully retrieved from the span's SpanContext. // It also ensures that returning false from the ForeachBaggageItem handler aborts iteration. func (s *APICheckSuite) TestMultiBaggage() { span := s.tracer.StartSpan("op") assertEmptyBaggage(s.T(), span.Context()) span.SetBaggageItem("Bag1", "BaggageVal1") span.SetBaggageItem("Bag2", "BaggageVal2") if s.opts.CheckBaggageValues { s.Equal("BaggageVal1", span.BaggageItem("Bag1")) s.Equal("BaggageVal2", span.BaggageItem("Bag2")) called := false span.Context().ForeachBaggageItem(func(k, v string) bool { s.False(called) // should only be called once called = true return false }) s.True(called) } else { s.T().Log("CheckBaggageValues capability not set, skipping") } span.Finish() } golang-github-opentracing-opentracing-go-1.2.0/harness/noop_api_test.go000066400000000000000000000005141377553415200263330ustar00rootroot00000000000000package harness import ( "testing" "github.com/opentracing/opentracing-go" ) func TestAPI(t *testing.T) { RunAPIChecks(t, func() (tracer opentracing.Tracer, closer func()) { return opentracing.NoopTracer{}, nil }, // NoopTracer doesn't do much CheckBaggageValues(false), CheckInject(false), CheckExtract(false), ) } golang-github-opentracing-opentracing-go-1.2.0/log/000077500000000000000000000000001377553415200222575ustar00rootroot00000000000000golang-github-opentracing-opentracing-go-1.2.0/log/field.go000066400000000000000000000162221377553415200236740ustar00rootroot00000000000000package log import ( "fmt" "math" ) type fieldType int const ( stringType fieldType = iota boolType intType int32Type uint32Type int64Type uint64Type float32Type float64Type errorType objectType lazyLoggerType noopType ) // Field instances are constructed via LogBool, LogString, and so on. // Tracing implementations may then handle them via the Field.Marshal // method. // // "heavily influenced by" (i.e., partially stolen from) // https://github.com/uber-go/zap type Field struct { key string fieldType fieldType numericVal int64 stringVal string interfaceVal interface{} } // String adds a string-valued key:value pair to a Span.LogFields() record func String(key, val string) Field { return Field{ key: key, fieldType: stringType, stringVal: val, } } // Bool adds a bool-valued key:value pair to a Span.LogFields() record func Bool(key string, val bool) Field { var numericVal int64 if val { numericVal = 1 } return Field{ key: key, fieldType: boolType, numericVal: numericVal, } } // Int adds an int-valued key:value pair to a Span.LogFields() record func Int(key string, val int) Field { return Field{ key: key, fieldType: intType, numericVal: int64(val), } } // Int32 adds an int32-valued key:value pair to a Span.LogFields() record func Int32(key string, val int32) Field { return Field{ key: key, fieldType: int32Type, numericVal: int64(val), } } // Int64 adds an int64-valued key:value pair to a Span.LogFields() record func Int64(key string, val int64) Field { return Field{ key: key, fieldType: int64Type, numericVal: val, } } // Uint32 adds a uint32-valued key:value pair to a Span.LogFields() record func Uint32(key string, val uint32) Field { return Field{ key: key, fieldType: uint32Type, numericVal: int64(val), } } // Uint64 adds a uint64-valued key:value pair to a Span.LogFields() record func Uint64(key string, val uint64) Field { return Field{ key: key, fieldType: uint64Type, numericVal: int64(val), } } // Float32 adds a float32-valued key:value pair to a Span.LogFields() record func Float32(key string, val float32) Field { return Field{ key: key, fieldType: float32Type, numericVal: int64(math.Float32bits(val)), } } // Float64 adds a float64-valued key:value pair to a Span.LogFields() record func Float64(key string, val float64) Field { return Field{ key: key, fieldType: float64Type, numericVal: int64(math.Float64bits(val)), } } // Error adds an error with the key "error.object" to a Span.LogFields() record func Error(err error) Field { return Field{ key: "error.object", fieldType: errorType, interfaceVal: err, } } // Object adds an object-valued key:value pair to a Span.LogFields() record // Please pass in an immutable object, otherwise there may be concurrency issues. // Such as passing in the map, log.Object may result in "fatal error: concurrent map iteration and map write". // Because span is sent asynchronously, it is possible that this map will also be modified. func Object(key string, obj interface{}) Field { return Field{ key: key, fieldType: objectType, interfaceVal: obj, } } // Event creates a string-valued Field for span logs with key="event" and value=val. func Event(val string) Field { return String("event", val) } // Message creates a string-valued Field for span logs with key="message" and value=val. func Message(val string) Field { return String("message", val) } // LazyLogger allows for user-defined, late-bound logging of arbitrary data type LazyLogger func(fv Encoder) // Lazy adds a LazyLogger to a Span.LogFields() record; the tracing // implementation will call the LazyLogger function at an indefinite time in // the future (after Lazy() returns). func Lazy(ll LazyLogger) Field { return Field{ fieldType: lazyLoggerType, interfaceVal: ll, } } // Noop creates a no-op log field that should be ignored by the tracer. // It can be used to capture optional fields, for example those that should // only be logged in non-production environment: // // func customerField(order *Order) log.Field { // if os.Getenv("ENVIRONMENT") == "dev" { // return log.String("customer", order.Customer.ID) // } // return log.Noop() // } // // span.LogFields(log.String("event", "purchase"), customerField(order)) // func Noop() Field { return Field{ fieldType: noopType, } } // Encoder allows access to the contents of a Field (via a call to // Field.Marshal). // // Tracer implementations typically provide an implementation of Encoder; // OpenTracing callers typically do not need to concern themselves with it. type Encoder interface { EmitString(key, value string) EmitBool(key string, value bool) EmitInt(key string, value int) EmitInt32(key string, value int32) EmitInt64(key string, value int64) EmitUint32(key string, value uint32) EmitUint64(key string, value uint64) EmitFloat32(key string, value float32) EmitFloat64(key string, value float64) EmitObject(key string, value interface{}) EmitLazyLogger(value LazyLogger) } // Marshal passes a Field instance through to the appropriate // field-type-specific method of an Encoder. func (lf Field) Marshal(visitor Encoder) { switch lf.fieldType { case stringType: visitor.EmitString(lf.key, lf.stringVal) case boolType: visitor.EmitBool(lf.key, lf.numericVal != 0) case intType: visitor.EmitInt(lf.key, int(lf.numericVal)) case int32Type: visitor.EmitInt32(lf.key, int32(lf.numericVal)) case int64Type: visitor.EmitInt64(lf.key, int64(lf.numericVal)) case uint32Type: visitor.EmitUint32(lf.key, uint32(lf.numericVal)) case uint64Type: visitor.EmitUint64(lf.key, uint64(lf.numericVal)) case float32Type: visitor.EmitFloat32(lf.key, math.Float32frombits(uint32(lf.numericVal))) case float64Type: visitor.EmitFloat64(lf.key, math.Float64frombits(uint64(lf.numericVal))) case errorType: if err, ok := lf.interfaceVal.(error); ok { visitor.EmitString(lf.key, err.Error()) } else { visitor.EmitString(lf.key, "") } case objectType: visitor.EmitObject(lf.key, lf.interfaceVal) case lazyLoggerType: visitor.EmitLazyLogger(lf.interfaceVal.(LazyLogger)) case noopType: // intentionally left blank } } // Key returns the field's key. func (lf Field) Key() string { return lf.key } // Value returns the field's value as interface{}. func (lf Field) Value() interface{} { switch lf.fieldType { case stringType: return lf.stringVal case boolType: return lf.numericVal != 0 case intType: return int(lf.numericVal) case int32Type: return int32(lf.numericVal) case int64Type: return int64(lf.numericVal) case uint32Type: return uint32(lf.numericVal) case uint64Type: return uint64(lf.numericVal) case float32Type: return math.Float32frombits(uint32(lf.numericVal)) case float64Type: return math.Float64frombits(uint64(lf.numericVal)) case errorType, objectType, lazyLoggerType: return lf.interfaceVal case noopType: return nil default: return nil } } // String returns a string representation of the key and value. func (lf Field) String() string { return fmt.Sprint(lf.key, ":", lf.Value()) } golang-github-opentracing-opentracing-go-1.2.0/log/field_test.go000066400000000000000000000017651377553415200247410ustar00rootroot00000000000000package log import ( "fmt" "testing" ) func TestFieldString(t *testing.T) { testCases := []struct { field Field expected string }{ { field: String("key", "value"), expected: "key:value", }, { field: Bool("key", true), expected: "key:true", }, { field: Int("key", 5), expected: "key:5", }, { field: Error(fmt.Errorf("err msg")), expected: "error.object:err msg", }, { field: Error(nil), expected: "error.object:", }, { field: Noop(), expected: ":", }, { field: Event("test"), expected: "event:test", }, { field: Message("test2"), expected: "message:test2", }, } for i, tc := range testCases { if str := tc.field.String(); str != tc.expected { t.Errorf("%d: expected '%s', got '%s'", i, tc.expected, str) } } } func TestNoopDoesNotMarshal(t *testing.T) { mockEncoder := struct { Encoder }{} f := Noop() f.Marshal(mockEncoder) // panics if any Encoder method is invoked } golang-github-opentracing-opentracing-go-1.2.0/log/util.go000066400000000000000000000031441377553415200235650ustar00rootroot00000000000000package log import ( "fmt" "reflect" ) // InterleavedKVToFields converts keyValues a la Span.LogKV() to a Field slice // a la Span.LogFields(). func InterleavedKVToFields(keyValues ...interface{}) ([]Field, error) { if len(keyValues)%2 != 0 { return nil, fmt.Errorf("non-even keyValues len: %d", len(keyValues)) } fields := make([]Field, len(keyValues)/2) for i := 0; i*2 < len(keyValues); i++ { key, ok := keyValues[i*2].(string) if !ok { return nil, fmt.Errorf( "non-string key (pair #%d): %T", i, keyValues[i*2]) } switch typedVal := keyValues[i*2+1].(type) { case bool: fields[i] = Bool(key, typedVal) case string: fields[i] = String(key, typedVal) case int: fields[i] = Int(key, typedVal) case int8: fields[i] = Int32(key, int32(typedVal)) case int16: fields[i] = Int32(key, int32(typedVal)) case int32: fields[i] = Int32(key, typedVal) case int64: fields[i] = Int64(key, typedVal) case uint: fields[i] = Uint64(key, uint64(typedVal)) case uint64: fields[i] = Uint64(key, typedVal) case uint8: fields[i] = Uint32(key, uint32(typedVal)) case uint16: fields[i] = Uint32(key, uint32(typedVal)) case uint32: fields[i] = Uint32(key, typedVal) case float32: fields[i] = Float32(key, typedVal) case float64: fields[i] = Float64(key, typedVal) default: if typedVal == nil || (reflect.ValueOf(typedVal).Kind() == reflect.Ptr && reflect.ValueOf(typedVal).IsNil()) { fields[i] = String(key, "nil") continue } // When in doubt, coerce to a string fields[i] = String(key, fmt.Sprint(typedVal)) } } return fields, nil } golang-github-opentracing-opentracing-go-1.2.0/log/util_test.go000066400000000000000000000033461377553415200246300ustar00rootroot00000000000000package log import ( "errors" "io" "testing" "github.com/stretchr/testify/assert" ) var nilInterface io.Reader func TestInterleavedKVToFields(t *testing.T) { tests := []struct { name string keyValues []interface{} want []Field wantErr bool }{ { "incorrect pair", []interface{}{"test"}, nil, true, }, { "non string key", []interface{}{struct{}{}, "foo"}, nil, true, }, { "happy path", []interface{}{ "bool", true, "string", "string", "int", int(1), "int8", int8(2), "int16", int16(3), "int64", int64(4), "uint", uint(5), "uint64", uint64(6), "uint8", uint8(7), "uint16", uint16(8), "uint32", uint32(9), "float32", float32(10), "float64", float64(11), "int32", int32(12), "stringer", errors.New("err"), "nilInterface", nilInterface, "nil", nil, }, []Field{ Bool("bool", true), String("string", "string"), Int("int", int(1)), Int32("int8", int32(2)), Int32("int16", int32(3)), Int64("int64", int64(4)), Uint64("uint", uint64(5)), Uint64("uint64", uint64(6)), Uint32("uint8", uint32(7)), Uint32("uint16", uint32(8)), Uint32("uint32", uint32(9)), Float32("float32", float32(10)), Float64("float64", float64(11)), Int32("int32", int32(12)), String("stringer", errors.New("err").Error()), String("nilInterface", "nil"), String("nil", "nil"), }, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := InterleavedKVToFields(tt.keyValues...) if (err != nil) != tt.wantErr { t.Errorf("InterleavedKVToFields() error = %v, wantErr %v", err, tt.wantErr) return } assert.Equal(t, tt.want, got) }) } } golang-github-opentracing-opentracing-go-1.2.0/mocktracer/000077500000000000000000000000001377553415200236305ustar00rootroot00000000000000golang-github-opentracing-opentracing-go-1.2.0/mocktracer/mocklogrecord.go000066400000000000000000000054111377553415200270120ustar00rootroot00000000000000package mocktracer import ( "fmt" "reflect" "time" "github.com/opentracing/opentracing-go/log" ) // MockLogRecord represents data logged to a Span via Span.LogFields or // Span.LogKV. type MockLogRecord struct { Timestamp time.Time Fields []MockKeyValue } // MockKeyValue represents a single key:value pair. type MockKeyValue struct { Key string // All MockLogRecord values are coerced to strings via fmt.Sprint(), though // we retain their type separately. ValueKind reflect.Kind ValueString string } // EmitString belongs to the log.Encoder interface func (m *MockKeyValue) EmitString(key, value string) { m.Key = key m.ValueKind = reflect.TypeOf(value).Kind() m.ValueString = fmt.Sprint(value) } // EmitBool belongs to the log.Encoder interface func (m *MockKeyValue) EmitBool(key string, value bool) { m.Key = key m.ValueKind = reflect.TypeOf(value).Kind() m.ValueString = fmt.Sprint(value) } // EmitInt belongs to the log.Encoder interface func (m *MockKeyValue) EmitInt(key string, value int) { m.Key = key m.ValueKind = reflect.TypeOf(value).Kind() m.ValueString = fmt.Sprint(value) } // EmitInt32 belongs to the log.Encoder interface func (m *MockKeyValue) EmitInt32(key string, value int32) { m.Key = key m.ValueKind = reflect.TypeOf(value).Kind() m.ValueString = fmt.Sprint(value) } // EmitInt64 belongs to the log.Encoder interface func (m *MockKeyValue) EmitInt64(key string, value int64) { m.Key = key m.ValueKind = reflect.TypeOf(value).Kind() m.ValueString = fmt.Sprint(value) } // EmitUint32 belongs to the log.Encoder interface func (m *MockKeyValue) EmitUint32(key string, value uint32) { m.Key = key m.ValueKind = reflect.TypeOf(value).Kind() m.ValueString = fmt.Sprint(value) } // EmitUint64 belongs to the log.Encoder interface func (m *MockKeyValue) EmitUint64(key string, value uint64) { m.Key = key m.ValueKind = reflect.TypeOf(value).Kind() m.ValueString = fmt.Sprint(value) } // EmitFloat32 belongs to the log.Encoder interface func (m *MockKeyValue) EmitFloat32(key string, value float32) { m.Key = key m.ValueKind = reflect.TypeOf(value).Kind() m.ValueString = fmt.Sprint(value) } // EmitFloat64 belongs to the log.Encoder interface func (m *MockKeyValue) EmitFloat64(key string, value float64) { m.Key = key m.ValueKind = reflect.TypeOf(value).Kind() m.ValueString = fmt.Sprint(value) } // EmitObject belongs to the log.Encoder interface func (m *MockKeyValue) EmitObject(key string, value interface{}) { m.Key = key m.ValueKind = reflect.TypeOf(value).Kind() m.ValueString = fmt.Sprint(value) } // EmitLazyLogger belongs to the log.Encoder interface func (m *MockKeyValue) EmitLazyLogger(value log.LazyLogger) { var meta MockKeyValue value(&meta) m.Key = meta.Key m.ValueKind = meta.ValueKind m.ValueString = meta.ValueString } golang-github-opentracing-opentracing-go-1.2.0/mocktracer/mockspan.go000066400000000000000000000162201377553415200257730ustar00rootroot00000000000000package mocktracer import ( "fmt" "sync" "sync/atomic" "time" "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" "github.com/opentracing/opentracing-go/log" ) // MockSpanContext is an opentracing.SpanContext implementation. // // It is entirely unsuitable for production use, but appropriate for tests // that want to verify tracing behavior in other frameworks/applications. // // By default all spans have Sampled=true flag, unless {"sampling.priority": 0} // tag is set. type MockSpanContext struct { TraceID int SpanID int Sampled bool Baggage map[string]string } var mockIDSource = uint32(42) func nextMockID() int { return int(atomic.AddUint32(&mockIDSource, 1)) } // ForeachBaggageItem belongs to the SpanContext interface func (c MockSpanContext) ForeachBaggageItem(handler func(k, v string) bool) { for k, v := range c.Baggage { if !handler(k, v) { break } } } // WithBaggageItem creates a new context with an extra baggage item. func (c MockSpanContext) WithBaggageItem(key, value string) MockSpanContext { var newBaggage map[string]string if c.Baggage == nil { newBaggage = map[string]string{key: value} } else { newBaggage = make(map[string]string, len(c.Baggage)+1) for k, v := range c.Baggage { newBaggage[k] = v } newBaggage[key] = value } // Use positional parameters so the compiler will help catch new fields. return MockSpanContext{c.TraceID, c.SpanID, c.Sampled, newBaggage} } // MockSpan is an opentracing.Span implementation that exports its internal // state for testing purposes. type MockSpan struct { sync.RWMutex ParentID int OperationName string StartTime time.Time FinishTime time.Time // All of the below are protected by the embedded RWMutex. SpanContext MockSpanContext tags map[string]interface{} logs []MockLogRecord tracer *MockTracer } func newMockSpan(t *MockTracer, name string, opts opentracing.StartSpanOptions) *MockSpan { tags := opts.Tags if tags == nil { tags = map[string]interface{}{} } traceID := nextMockID() parentID := int(0) var baggage map[string]string sampled := true if len(opts.References) > 0 { traceID = opts.References[0].ReferencedContext.(MockSpanContext).TraceID parentID = opts.References[0].ReferencedContext.(MockSpanContext).SpanID sampled = opts.References[0].ReferencedContext.(MockSpanContext).Sampled baggage = opts.References[0].ReferencedContext.(MockSpanContext).Baggage } spanContext := MockSpanContext{traceID, nextMockID(), sampled, baggage} startTime := opts.StartTime if startTime.IsZero() { startTime = time.Now() } return &MockSpan{ ParentID: parentID, OperationName: name, StartTime: startTime, tags: tags, logs: []MockLogRecord{}, SpanContext: spanContext, tracer: t, } } // Tags returns a copy of tags accumulated by the span so far func (s *MockSpan) Tags() map[string]interface{} { s.RLock() defer s.RUnlock() tags := make(map[string]interface{}) for k, v := range s.tags { tags[k] = v } return tags } // Tag returns a single tag func (s *MockSpan) Tag(k string) interface{} { s.RLock() defer s.RUnlock() return s.tags[k] } // Logs returns a copy of logs accumulated in the span so far func (s *MockSpan) Logs() []MockLogRecord { s.RLock() defer s.RUnlock() logs := make([]MockLogRecord, len(s.logs)) copy(logs, s.logs) return logs } // Context belongs to the Span interface func (s *MockSpan) Context() opentracing.SpanContext { s.Lock() defer s.Unlock() return s.SpanContext } // SetTag belongs to the Span interface func (s *MockSpan) SetTag(key string, value interface{}) opentracing.Span { s.Lock() defer s.Unlock() if key == string(ext.SamplingPriority) { if v, ok := value.(uint16); ok { s.SpanContext.Sampled = v > 0 return s } if v, ok := value.(int); ok { s.SpanContext.Sampled = v > 0 return s } } s.tags[key] = value return s } // SetBaggageItem belongs to the Span interface func (s *MockSpan) SetBaggageItem(key, val string) opentracing.Span { s.Lock() defer s.Unlock() s.SpanContext = s.SpanContext.WithBaggageItem(key, val) return s } // BaggageItem belongs to the Span interface func (s *MockSpan) BaggageItem(key string) string { s.RLock() defer s.RUnlock() return s.SpanContext.Baggage[key] } // Finish belongs to the Span interface func (s *MockSpan) Finish() { s.Lock() s.FinishTime = time.Now() s.Unlock() s.tracer.recordSpan(s) } // FinishWithOptions belongs to the Span interface func (s *MockSpan) FinishWithOptions(opts opentracing.FinishOptions) { s.Lock() s.FinishTime = opts.FinishTime s.Unlock() // Handle any late-bound LogRecords. for _, lr := range opts.LogRecords { s.logFieldsWithTimestamp(lr.Timestamp, lr.Fields...) } // Handle (deprecated) BulkLogData. for _, ld := range opts.BulkLogData { if ld.Payload != nil { s.logFieldsWithTimestamp( ld.Timestamp, log.String("event", ld.Event), log.Object("payload", ld.Payload)) } else { s.logFieldsWithTimestamp( ld.Timestamp, log.String("event", ld.Event)) } } s.tracer.recordSpan(s) } // String allows printing span for debugging func (s *MockSpan) String() string { return fmt.Sprintf( "traceId=%d, spanId=%d, parentId=%d, sampled=%t, name=%s", s.SpanContext.TraceID, s.SpanContext.SpanID, s.ParentID, s.SpanContext.Sampled, s.OperationName) } // LogFields belongs to the Span interface func (s *MockSpan) LogFields(fields ...log.Field) { s.logFieldsWithTimestamp(time.Now(), fields...) } // The caller MUST NOT hold s.Lock func (s *MockSpan) logFieldsWithTimestamp(ts time.Time, fields ...log.Field) { lr := MockLogRecord{ Timestamp: ts, Fields: make([]MockKeyValue, len(fields)), } for i, f := range fields { outField := &(lr.Fields[i]) f.Marshal(outField) } s.Lock() defer s.Unlock() s.logs = append(s.logs, lr) } // LogKV belongs to the Span interface. // // This implementations coerces all "values" to strings, though that is not // something all implementations need to do. Indeed, a motivated person can and // probably should have this do a typed switch on the values. func (s *MockSpan) LogKV(keyValues ...interface{}) { if len(keyValues)%2 != 0 { s.LogFields(log.Error(fmt.Errorf("Non-even keyValues len: %v", len(keyValues)))) return } fields, err := log.InterleavedKVToFields(keyValues...) if err != nil { s.LogFields(log.Error(err), log.String("function", "LogKV")) return } s.LogFields(fields...) } // LogEvent belongs to the Span interface func (s *MockSpan) LogEvent(event string) { s.LogFields(log.String("event", event)) } // LogEventWithPayload belongs to the Span interface func (s *MockSpan) LogEventWithPayload(event string, payload interface{}) { s.LogFields(log.String("event", event), log.Object("payload", payload)) } // Log belongs to the Span interface func (s *MockSpan) Log(data opentracing.LogData) { panic("MockSpan.Log() no longer supported") } // SetOperationName belongs to the Span interface func (s *MockSpan) SetOperationName(operationName string) opentracing.Span { s.Lock() defer s.Unlock() s.OperationName = operationName return s } // Tracer belongs to the Span interface func (s *MockSpan) Tracer() opentracing.Tracer { return s.tracer } golang-github-opentracing-opentracing-go-1.2.0/mocktracer/mocktracer.go000066400000000000000000000061751377553415200263220ustar00rootroot00000000000000package mocktracer import ( "sync" "github.com/opentracing/opentracing-go" ) // New returns a MockTracer opentracing.Tracer implementation that's intended // to facilitate tests of OpenTracing instrumentation. func New() *MockTracer { t := &MockTracer{ finishedSpans: []*MockSpan{}, injectors: make(map[interface{}]Injector), extractors: make(map[interface{}]Extractor), } // register default injectors/extractors textPropagator := new(TextMapPropagator) t.RegisterInjector(opentracing.TextMap, textPropagator) t.RegisterExtractor(opentracing.TextMap, textPropagator) httpPropagator := &TextMapPropagator{HTTPHeaders: true} t.RegisterInjector(opentracing.HTTPHeaders, httpPropagator) t.RegisterExtractor(opentracing.HTTPHeaders, httpPropagator) return t } // MockTracer is only intended for testing OpenTracing instrumentation. // // It is entirely unsuitable for production use, but appropriate for tests // that want to verify tracing behavior in other frameworks/applications. type MockTracer struct { sync.RWMutex finishedSpans []*MockSpan injectors map[interface{}]Injector extractors map[interface{}]Extractor } // FinishedSpans returns all spans that have been Finish()'ed since the // MockTracer was constructed or since the last call to its Reset() method. func (t *MockTracer) FinishedSpans() []*MockSpan { t.RLock() defer t.RUnlock() spans := make([]*MockSpan, len(t.finishedSpans)) copy(spans, t.finishedSpans) return spans } // Reset clears the internally accumulated finished spans. Note that any // extant MockSpans will still append to finishedSpans when they Finish(), // even after a call to Reset(). func (t *MockTracer) Reset() { t.Lock() defer t.Unlock() t.finishedSpans = []*MockSpan{} } // StartSpan belongs to the Tracer interface. func (t *MockTracer) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span { sso := opentracing.StartSpanOptions{} for _, o := range opts { o.Apply(&sso) } return newMockSpan(t, operationName, sso) } // RegisterInjector registers injector for given format func (t *MockTracer) RegisterInjector(format interface{}, injector Injector) { t.injectors[format] = injector } // RegisterExtractor registers extractor for given format func (t *MockTracer) RegisterExtractor(format interface{}, extractor Extractor) { t.extractors[format] = extractor } // Inject belongs to the Tracer interface. func (t *MockTracer) Inject(sm opentracing.SpanContext, format interface{}, carrier interface{}) error { spanContext, ok := sm.(MockSpanContext) if !ok { return opentracing.ErrInvalidSpanContext } injector, ok := t.injectors[format] if !ok { return opentracing.ErrUnsupportedFormat } return injector.Inject(spanContext, carrier) } // Extract belongs to the Tracer interface. func (t *MockTracer) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) { extractor, ok := t.extractors[format] if !ok { return nil, opentracing.ErrUnsupportedFormat } return extractor.Extract(carrier) } func (t *MockTracer) recordSpan(span *MockSpan) { t.Lock() defer t.Unlock() t.finishedSpans = append(t.finishedSpans, span) } golang-github-opentracing-opentracing-go-1.2.0/mocktracer/mocktracer_test.go000066400000000000000000000201631377553415200273520ustar00rootroot00000000000000package mocktracer import ( "net/http" "reflect" "sync" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" "github.com/opentracing/opentracing-go/log" ) func TestMockTracer_StartSpan(t *testing.T) { tracer := New() span1 := tracer.StartSpan( "a", opentracing.Tags(map[string]interface{}{"x": "y"})) span2 := span1.Tracer().StartSpan( "", opentracing.ChildOf(span1.Context())) span2.Finish() span1.Finish() spans := tracer.FinishedSpans() assert.Equal(t, 2, len(spans)) parent := spans[1] child := spans[0] assert.Equal(t, map[string]interface{}{"x": "y"}, parent.Tags()) assert.Equal(t, child.ParentID, parent.Context().(MockSpanContext).SpanID) } func TestMockSpan_SetOperationName(t *testing.T) { tracer := New() span := tracer.StartSpan("") span.SetOperationName("x") assert.Equal(t, "x", span.(*MockSpan).OperationName) } func TestMockSpanContext_Baggage(t *testing.T) { tracer := New() span := tracer.StartSpan("x") span.SetBaggageItem("x", "y") assert.Equal(t, "y", span.BaggageItem("x")) assert.Equal(t, map[string]string{"x": "y"}, span.Context().(MockSpanContext).Baggage) baggage := make(map[string]string) span.Context().ForeachBaggageItem(func(k, v string) bool { baggage[k] = v return true }) assert.Equal(t, map[string]string{"x": "y"}, baggage) span.SetBaggageItem("a", "b") baggage = make(map[string]string) span.Context().ForeachBaggageItem(func(k, v string) bool { baggage[k] = v return false // exit early }) assert.Equal(t, 2, len(span.Context().(MockSpanContext).Baggage)) assert.Equal(t, 1, len(baggage)) } func TestMockSpan_Tag(t *testing.T) { tracer := New() span := tracer.StartSpan("x") span.SetTag("x", "y") assert.Equal(t, "y", span.(*MockSpan).Tag("x")) } func TestMockSpan_Tags(t *testing.T) { tracer := New() span := tracer.StartSpan("x") span.SetTag("x", "y") assert.Equal(t, map[string]interface{}{"x": "y"}, span.(*MockSpan).Tags()) } func TestMockTracer_FinishedSpans_and_Reset(t *testing.T) { tracer := New() span := tracer.StartSpan("x") span.SetTag("x", "y") span.Finish() spans := tracer.FinishedSpans() assert.Equal(t, 1, len(spans)) assert.Equal(t, map[string]interface{}{"x": "y"}, spans[0].Tags()) tracer.Reset() spans = tracer.FinishedSpans() assert.Equal(t, 0, len(spans)) } func zeroOutTimestamps(recs []MockLogRecord) { for i := range recs { recs[i].Timestamp = time.Time{} } } func TestMockSpan_LogKV(t *testing.T) { tracer := New() span := tracer.StartSpan("s") span.LogKV("key0", "string0") span.LogKV("key1", "string1", "key2", uint32(42)) span.Finish() spans := tracer.FinishedSpans() assert.Equal(t, 1, len(spans)) actual := spans[0].Logs() zeroOutTimestamps(actual) assert.Equal(t, []MockLogRecord{ MockLogRecord{ Fields: []MockKeyValue{ MockKeyValue{Key: "key0", ValueKind: reflect.String, ValueString: "string0"}, }, }, MockLogRecord{ Fields: []MockKeyValue{ MockKeyValue{Key: "key1", ValueKind: reflect.String, ValueString: "string1"}, MockKeyValue{Key: "key2", ValueKind: reflect.Uint32, ValueString: "42"}, }, }, }, actual) } func TestMockSpan_LogFields(t *testing.T) { tracer := New() span := tracer.StartSpan("s") span.LogFields(log.String("key0", "string0")) span.LogFields(log.String("key1", "string1"), log.Uint32("key2", uint32(42))) span.LogFields(log.Lazy(func(fv log.Encoder) { fv.EmitInt("key_lazy", 12) })) span.FinishWithOptions(opentracing.FinishOptions{ LogRecords: []opentracing.LogRecord{ {Timestamp: time.Now(), Fields: []log.Field{log.String("key9", "finish")}}, }}) spans := tracer.FinishedSpans() assert.Equal(t, 1, len(spans)) actual := spans[0].Logs() zeroOutTimestamps(actual) assert.Equal(t, []MockLogRecord{ MockLogRecord{ Fields: []MockKeyValue{ MockKeyValue{Key: "key0", ValueKind: reflect.String, ValueString: "string0"}, }, }, MockLogRecord{ Fields: []MockKeyValue{ MockKeyValue{Key: "key1", ValueKind: reflect.String, ValueString: "string1"}, MockKeyValue{Key: "key2", ValueKind: reflect.Uint32, ValueString: "42"}, }, }, MockLogRecord{ Fields: []MockKeyValue{ // Note that the LazyLogger gets to control the key as well as the value. MockKeyValue{Key: "key_lazy", ValueKind: reflect.Int, ValueString: "12"}, }, }, MockLogRecord{ Fields: []MockKeyValue{ MockKeyValue{Key: "key9", ValueKind: reflect.String, ValueString: "finish"}, }, }, }, actual) } func TestMockSpan_DeprecatedLogs(t *testing.T) { tracer := New() span := tracer.StartSpan("x") span.LogEvent("x") span.LogEventWithPayload("y", "z") span.LogEvent("a") span.FinishWithOptions(opentracing.FinishOptions{ BulkLogData: []opentracing.LogData{{Event: "f"}}}) spans := tracer.FinishedSpans() assert.Equal(t, 1, len(spans)) actual := spans[0].Logs() zeroOutTimestamps(actual) assert.Equal(t, []MockLogRecord{ MockLogRecord{ Fields: []MockKeyValue{ MockKeyValue{Key: "event", ValueKind: reflect.String, ValueString: "x"}, }, }, MockLogRecord{ Fields: []MockKeyValue{ MockKeyValue{Key: "event", ValueKind: reflect.String, ValueString: "y"}, MockKeyValue{Key: "payload", ValueKind: reflect.String, ValueString: "z"}, }, }, MockLogRecord{ Fields: []MockKeyValue{ MockKeyValue{Key: "event", ValueKind: reflect.String, ValueString: "a"}, }, }, MockLogRecord{ Fields: []MockKeyValue{ MockKeyValue{Key: "event", ValueKind: reflect.String, ValueString: "f"}, }, }, }, actual) } func TestMockTracer_Propagation(t *testing.T) { textCarrier := func() interface{} { return opentracing.TextMapCarrier(make(map[string]string)) } textLen := func(c interface{}) int { return len(c.(opentracing.TextMapCarrier)) } httpCarrier := func() interface{} { httpHeaders := http.Header(make(map[string][]string)) return opentracing.HTTPHeadersCarrier(httpHeaders) } httpLen := func(c interface{}) int { return len(c.(opentracing.HTTPHeadersCarrier)) } tests := []struct { sampled bool format opentracing.BuiltinFormat carrier func() interface{} len func(interface{}) int }{ {sampled: true, format: opentracing.TextMap, carrier: textCarrier, len: textLen}, {sampled: false, format: opentracing.TextMap, carrier: textCarrier, len: textLen}, {sampled: true, format: opentracing.HTTPHeaders, carrier: httpCarrier, len: httpLen}, {sampled: false, format: opentracing.HTTPHeaders, carrier: httpCarrier, len: httpLen}, } for _, test := range tests { tracer := New() span := tracer.StartSpan("x") span.SetBaggageItem("x", "y:z") // colon should be URL encoded as %3A if !test.sampled { ext.SamplingPriority.Set(span, 0) } mSpan := span.(*MockSpan) assert.Equal(t, opentracing.ErrUnsupportedFormat, tracer.Inject(span.Context(), opentracing.Binary, nil)) assert.Equal(t, opentracing.ErrInvalidCarrier, tracer.Inject(span.Context(), opentracing.TextMap, span)) carrier := test.carrier() err := tracer.Inject(span.Context(), test.format, carrier) require.NoError(t, err) assert.Equal(t, 4, test.len(carrier), "expect baggage + 2 ids + sampled") if test.format == opentracing.HTTPHeaders { c := carrier.(opentracing.HTTPHeadersCarrier) assert.Equal(t, "y%3Az", c["Mockpfx-Baggage-X"][0]) } _, err = tracer.Extract(opentracing.Binary, nil) assert.Equal(t, opentracing.ErrUnsupportedFormat, err) _, err = tracer.Extract(opentracing.TextMap, tracer) assert.Equal(t, opentracing.ErrInvalidCarrier, err) extractedContext, err := tracer.Extract(test.format, carrier) require.NoError(t, err) assert.Equal(t, mSpan.SpanContext.TraceID, extractedContext.(MockSpanContext).TraceID) assert.Equal(t, mSpan.SpanContext.SpanID, extractedContext.(MockSpanContext).SpanID) assert.Equal(t, test.sampled, extractedContext.(MockSpanContext).Sampled) assert.Equal(t, "y:z", extractedContext.(MockSpanContext).Baggage["x"]) } } func TestMockSpan_Races(t *testing.T) { span := New().StartSpan("x") var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() span.SetBaggageItem("test_key", "test_value") }() go func() { defer wg.Done() span.Context() }() wg.Wait() } golang-github-opentracing-opentracing-go-1.2.0/mocktracer/propagation.go000066400000000000000000000072621377553415200265110ustar00rootroot00000000000000package mocktracer import ( "fmt" "net/url" "strconv" "strings" "github.com/opentracing/opentracing-go" ) const mockTextMapIdsPrefix = "mockpfx-ids-" const mockTextMapBaggagePrefix = "mockpfx-baggage-" var emptyContext = MockSpanContext{} // Injector is responsible for injecting SpanContext instances in a manner suitable // for propagation via a format-specific "carrier" object. Typically the // injection will take place across an RPC boundary, but message queues and // other IPC mechanisms are also reasonable places to use an Injector. type Injector interface { // Inject takes `SpanContext` and injects it into `carrier`. The actual type // of `carrier` depends on the `format` passed to `Tracer.Inject()`. // // Implementations may return opentracing.ErrInvalidCarrier or any other // implementation-specific error if injection fails. Inject(ctx MockSpanContext, carrier interface{}) error } // Extractor is responsible for extracting SpanContext instances from a // format-specific "carrier" object. Typically the extraction will take place // on the server side of an RPC boundary, but message queues and other IPC // mechanisms are also reasonable places to use an Extractor. type Extractor interface { // Extract decodes a SpanContext instance from the given `carrier`, // or (nil, opentracing.ErrSpanContextNotFound) if no context could // be found in the `carrier`. Extract(carrier interface{}) (MockSpanContext, error) } // TextMapPropagator implements Injector/Extractor for TextMap and HTTPHeaders formats. type TextMapPropagator struct { HTTPHeaders bool } // Inject implements the Injector interface func (t *TextMapPropagator) Inject(spanContext MockSpanContext, carrier interface{}) error { writer, ok := carrier.(opentracing.TextMapWriter) if !ok { return opentracing.ErrInvalidCarrier } // Ids: writer.Set(mockTextMapIdsPrefix+"traceid", strconv.Itoa(spanContext.TraceID)) writer.Set(mockTextMapIdsPrefix+"spanid", strconv.Itoa(spanContext.SpanID)) writer.Set(mockTextMapIdsPrefix+"sampled", fmt.Sprint(spanContext.Sampled)) // Baggage: for baggageKey, baggageVal := range spanContext.Baggage { safeVal := baggageVal if t.HTTPHeaders { safeVal = url.QueryEscape(baggageVal) } writer.Set(mockTextMapBaggagePrefix+baggageKey, safeVal) } return nil } // Extract implements the Extractor interface func (t *TextMapPropagator) Extract(carrier interface{}) (MockSpanContext, error) { reader, ok := carrier.(opentracing.TextMapReader) if !ok { return emptyContext, opentracing.ErrInvalidCarrier } rval := MockSpanContext{0, 0, true, nil} err := reader.ForeachKey(func(key, val string) error { lowerKey := strings.ToLower(key) switch { case lowerKey == mockTextMapIdsPrefix+"traceid": // Ids: i, err := strconv.Atoi(val) if err != nil { return err } rval.TraceID = i case lowerKey == mockTextMapIdsPrefix+"spanid": // Ids: i, err := strconv.Atoi(val) if err != nil { return err } rval.SpanID = i case lowerKey == mockTextMapIdsPrefix+"sampled": b, err := strconv.ParseBool(val) if err != nil { return err } rval.Sampled = b case strings.HasPrefix(lowerKey, mockTextMapBaggagePrefix): // Baggage: if rval.Baggage == nil { rval.Baggage = make(map[string]string) } safeVal := val if t.HTTPHeaders { // unescape errors are ignored, nothing can be done if rawVal, err := url.QueryUnescape(val); err == nil { safeVal = rawVal } } rval.Baggage[lowerKey[len(mockTextMapBaggagePrefix):]] = safeVal } return nil }) if rval.TraceID == 0 || rval.SpanID == 0 { return emptyContext, opentracing.ErrSpanContextNotFound } if err != nil { return emptyContext, err } return rval, nil } golang-github-opentracing-opentracing-go-1.2.0/noop.go000066400000000000000000000051641377553415200230060ustar00rootroot00000000000000package opentracing import "github.com/opentracing/opentracing-go/log" // A NoopTracer is a trivial, minimum overhead implementation of Tracer // for which all operations are no-ops. // // The primary use of this implementation is in libraries, such as RPC // frameworks, that make tracing an optional feature controlled by the // end user. A no-op implementation allows said libraries to use it // as the default Tracer and to write instrumentation that does // not need to keep checking if the tracer instance is nil. // // For the same reason, the NoopTracer is the default "global" tracer // (see GlobalTracer and SetGlobalTracer functions). // // WARNING: NoopTracer does not support baggage propagation. type NoopTracer struct{} type noopSpan struct{} type noopSpanContext struct{} var ( defaultNoopSpanContext SpanContext = noopSpanContext{} defaultNoopSpan Span = noopSpan{} defaultNoopTracer Tracer = NoopTracer{} ) const ( emptyString = "" ) // noopSpanContext: func (n noopSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {} // noopSpan: func (n noopSpan) Context() SpanContext { return defaultNoopSpanContext } func (n noopSpan) SetBaggageItem(key, val string) Span { return n } func (n noopSpan) BaggageItem(key string) string { return emptyString } func (n noopSpan) SetTag(key string, value interface{}) Span { return n } func (n noopSpan) LogFields(fields ...log.Field) {} func (n noopSpan) LogKV(keyVals ...interface{}) {} func (n noopSpan) Finish() {} func (n noopSpan) FinishWithOptions(opts FinishOptions) {} func (n noopSpan) SetOperationName(operationName string) Span { return n } func (n noopSpan) Tracer() Tracer { return defaultNoopTracer } func (n noopSpan) LogEvent(event string) {} func (n noopSpan) LogEventWithPayload(event string, payload interface{}) {} func (n noopSpan) Log(data LogData) {} // StartSpan belongs to the Tracer interface. func (n NoopTracer) StartSpan(operationName string, opts ...StartSpanOption) Span { return defaultNoopSpan } // Inject belongs to the Tracer interface. func (n NoopTracer) Inject(sp SpanContext, format interface{}, carrier interface{}) error { return nil } // Extract belongs to the Tracer interface. func (n NoopTracer) Extract(format interface{}, carrier interface{}) (SpanContext, error) { return nil, ErrSpanContextNotFound } golang-github-opentracing-opentracing-go-1.2.0/options_test.go000066400000000000000000000013461377553415200245630ustar00rootroot00000000000000package opentracing import ( "testing" "github.com/stretchr/testify/require" ) func TestChildOfAndFollowsFrom(t *testing.T) { tests := []struct { newOpt func(SpanContext) SpanReference refType SpanReferenceType name string }{ {ChildOf, ChildOfRef, "ChildOf"}, {FollowsFrom, FollowsFromRef, "FollowsFrom"}, } for _, test := range tests { opts := new(StartSpanOptions) test.newOpt(nil).Apply(opts) require.Nil(t, opts.References, "%s(nil) must not append a reference", test.name) ctx := new(noopSpanContext) test.newOpt(ctx).Apply(opts) require.Equal(t, []SpanReference{ SpanReference{ReferencedContext: ctx, Type: test.refType}, }, opts.References, "%s(ctx) must append a reference", test.name) } } golang-github-opentracing-opentracing-go-1.2.0/propagation.go000066400000000000000000000144621377553415200243570ustar00rootroot00000000000000package opentracing import ( "errors" "net/http" ) /////////////////////////////////////////////////////////////////////////////// // CORE PROPAGATION INTERFACES: /////////////////////////////////////////////////////////////////////////////// var ( // ErrUnsupportedFormat occurs when the `format` passed to Tracer.Inject() or // Tracer.Extract() is not recognized by the Tracer implementation. ErrUnsupportedFormat = errors.New("opentracing: Unknown or unsupported Inject/Extract format") // ErrSpanContextNotFound occurs when the `carrier` passed to // Tracer.Extract() is valid and uncorrupted but has insufficient // information to extract a SpanContext. ErrSpanContextNotFound = errors.New("opentracing: SpanContext not found in Extract carrier") // ErrInvalidSpanContext errors occur when Tracer.Inject() is asked to // operate on a SpanContext which it is not prepared to handle (for // example, since it was created by a different tracer implementation). ErrInvalidSpanContext = errors.New("opentracing: SpanContext type incompatible with tracer") // ErrInvalidCarrier errors occur when Tracer.Inject() or Tracer.Extract() // implementations expect a different type of `carrier` than they are // given. ErrInvalidCarrier = errors.New("opentracing: Invalid Inject/Extract carrier") // ErrSpanContextCorrupted occurs when the `carrier` passed to // Tracer.Extract() is of the expected type but is corrupted. ErrSpanContextCorrupted = errors.New("opentracing: SpanContext data corrupted in Extract carrier") ) /////////////////////////////////////////////////////////////////////////////// // BUILTIN PROPAGATION FORMATS: /////////////////////////////////////////////////////////////////////////////// // BuiltinFormat is used to demarcate the values within package `opentracing` // that are intended for use with the Tracer.Inject() and Tracer.Extract() // methods. type BuiltinFormat byte const ( // Binary represents SpanContexts as opaque binary data. // // For Tracer.Inject(): the carrier must be an `io.Writer`. // // For Tracer.Extract(): the carrier must be an `io.Reader`. Binary BuiltinFormat = iota // TextMap represents SpanContexts as key:value string pairs. // // Unlike HTTPHeaders, the TextMap format does not restrict the key or // value character sets in any way. // // For Tracer.Inject(): the carrier must be a `TextMapWriter`. // // For Tracer.Extract(): the carrier must be a `TextMapReader`. TextMap // HTTPHeaders represents SpanContexts as HTTP header string pairs. // // Unlike TextMap, the HTTPHeaders format requires that the keys and values // be valid as HTTP headers as-is (i.e., character casing may be unstable // and special characters are disallowed in keys, values should be // URL-escaped, etc). // // For Tracer.Inject(): the carrier must be a `TextMapWriter`. // // For Tracer.Extract(): the carrier must be a `TextMapReader`. // // See HTTPHeadersCarrier for an implementation of both TextMapWriter // and TextMapReader that defers to an http.Header instance for storage. // For example, Inject(): // // carrier := opentracing.HTTPHeadersCarrier(httpReq.Header) // err := span.Tracer().Inject( // span.Context(), opentracing.HTTPHeaders, carrier) // // Or Extract(): // // carrier := opentracing.HTTPHeadersCarrier(httpReq.Header) // clientContext, err := tracer.Extract( // opentracing.HTTPHeaders, carrier) // HTTPHeaders ) // TextMapWriter is the Inject() carrier for the TextMap builtin format. With // it, the caller can encode a SpanContext for propagation as entries in a map // of unicode strings. type TextMapWriter interface { // Set a key:value pair to the carrier. Multiple calls to Set() for the // same key leads to undefined behavior. // // NOTE: The backing store for the TextMapWriter may contain data unrelated // to SpanContext. As such, Inject() and Extract() implementations that // call the TextMapWriter and TextMapReader interfaces must agree on a // prefix or other convention to distinguish their own key:value pairs. Set(key, val string) } // TextMapReader is the Extract() carrier for the TextMap builtin format. With it, // the caller can decode a propagated SpanContext as entries in a map of // unicode strings. type TextMapReader interface { // ForeachKey returns TextMap contents via repeated calls to the `handler` // function. If any call to `handler` returns a non-nil error, ForeachKey // terminates and returns that error. // // NOTE: The backing store for the TextMapReader may contain data unrelated // to SpanContext. As such, Inject() and Extract() implementations that // call the TextMapWriter and TextMapReader interfaces must agree on a // prefix or other convention to distinguish their own key:value pairs. // // The "foreach" callback pattern reduces unnecessary copying in some cases // and also allows implementations to hold locks while the map is read. ForeachKey(handler func(key, val string) error) error } // TextMapCarrier allows the use of regular map[string]string // as both TextMapWriter and TextMapReader. type TextMapCarrier map[string]string // ForeachKey conforms to the TextMapReader interface. func (c TextMapCarrier) ForeachKey(handler func(key, val string) error) error { for k, v := range c { if err := handler(k, v); err != nil { return err } } return nil } // Set implements Set() of opentracing.TextMapWriter func (c TextMapCarrier) Set(key, val string) { c[key] = val } // HTTPHeadersCarrier satisfies both TextMapWriter and TextMapReader. // // Example usage for server side: // // carrier := opentracing.HTTPHeadersCarrier(httpReq.Header) // clientContext, err := tracer.Extract(opentracing.HTTPHeaders, carrier) // // Example usage for client side: // // carrier := opentracing.HTTPHeadersCarrier(httpReq.Header) // err := tracer.Inject( // span.Context(), // opentracing.HTTPHeaders, // carrier) // type HTTPHeadersCarrier http.Header // Set conforms to the TextMapWriter interface. func (c HTTPHeadersCarrier) Set(key, val string) { h := http.Header(c) h.Set(key, val) } // ForeachKey conforms to the TextMapReader interface. func (c HTTPHeadersCarrier) ForeachKey(handler func(key, val string) error) error { for k, vals := range c { for _, v := range vals { if err := handler(k, v); err != nil { return err } } } return nil } golang-github-opentracing-opentracing-go-1.2.0/propagation_test.go000066400000000000000000000044111377553415200254070ustar00rootroot00000000000000package opentracing import ( "net/http" "strconv" "testing" ) const testHeaderPrefix = "testprefix-" func TestTextMapCarrierInject(t *testing.T) { m := make(map[string]string) m["NotOT"] = "blah" m["opname"] = "AlsoNotOT" tracer := testTracer{} span := tracer.StartSpan("someSpan") fakeID := span.Context().(testSpanContext).FakeID carrier := TextMapCarrier(m) if err := span.Tracer().Inject(span.Context(), TextMap, carrier); err != nil { t.Fatal(err) } if len(m) != 3 { t.Errorf("Unexpected header length: %v", len(m)) } // The prefix comes from just above; the suffix comes from // testTracer.Inject(). if m["testprefix-fakeid"] != strconv.Itoa(fakeID) { t.Errorf("Could not find fakeid at expected key") } } func TestTextMapCarrierExtract(t *testing.T) { m := make(map[string]string) m["NotOT"] = "blah" m["opname"] = "AlsoNotOT" m["testprefix-fakeid"] = "42" tracer := testTracer{} carrier := TextMapCarrier(m) extractedContext, err := tracer.Extract(TextMap, carrier) if err != nil { t.Fatal(err) } if extractedContext.(testSpanContext).FakeID != 42 { t.Errorf("Failed to read testprefix-fakeid correctly") } } func TestHTTPHeaderInject(t *testing.T) { h := http.Header{} h.Add("NotOT", "blah") h.Add("opname", "AlsoNotOT") tracer := testTracer{} span := tracer.StartSpan("someSpan") fakeID := span.Context().(testSpanContext).FakeID // Use HTTPHeadersCarrier to wrap around `h`. carrier := HTTPHeadersCarrier(h) if err := span.Tracer().Inject(span.Context(), HTTPHeaders, carrier); err != nil { t.Fatal(err) } if len(h) != 3 { t.Errorf("Unexpected header length: %v", len(h)) } // The prefix comes from just above; the suffix comes from // testTracer.Inject(). if h.Get("testprefix-fakeid") != strconv.Itoa(fakeID) { t.Errorf("Could not find fakeid at expected key") } } func TestHTTPHeaderExtract(t *testing.T) { h := http.Header{} h.Add("NotOT", "blah") h.Add("opname", "AlsoNotOT") h.Add("testprefix-fakeid", "42") tracer := testTracer{} // Use HTTPHeadersCarrier to wrap around `h`. carrier := HTTPHeadersCarrier(h) spanContext, err := tracer.Extract(HTTPHeaders, carrier) if err != nil { t.Fatal(err) } if spanContext.(testSpanContext).FakeID != 42 { t.Errorf("Failed to read testprefix-fakeid correctly") } } golang-github-opentracing-opentracing-go-1.2.0/span.go000066400000000000000000000150521377553415200227710ustar00rootroot00000000000000package opentracing import ( "time" "github.com/opentracing/opentracing-go/log" ) // SpanContext represents Span state that must propagate to descendant Spans and across process // boundaries (e.g., a tuple). type SpanContext interface { // ForeachBaggageItem grants access to all baggage items stored in the // SpanContext. // The handler function will be called for each baggage key/value pair. // The ordering of items is not guaranteed. // // The bool return value indicates if the handler wants to continue iterating // through the rest of the baggage items; for example if the handler is trying to // find some baggage item by pattern matching the name, it can return false // as soon as the item is found to stop further iterations. ForeachBaggageItem(handler func(k, v string) bool) } // Span represents an active, un-finished span in the OpenTracing system. // // Spans are created by the Tracer interface. type Span interface { // Sets the end timestamp and finalizes Span state. // // With the exception of calls to Context() (which are always allowed), // Finish() must be the last call made to any span instance, and to do // otherwise leads to undefined behavior. Finish() // FinishWithOptions is like Finish() but with explicit control over // timestamps and log data. FinishWithOptions(opts FinishOptions) // Context() yields the SpanContext for this Span. Note that the return // value of Context() is still valid after a call to Span.Finish(), as is // a call to Span.Context() after a call to Span.Finish(). Context() SpanContext // Sets or changes the operation name. // // Returns a reference to this Span for chaining. SetOperationName(operationName string) Span // Adds a tag to the span. // // If there is a pre-existing tag set for `key`, it is overwritten. // // Tag values can be numeric types, strings, or bools. The behavior of // other tag value types is undefined at the OpenTracing level. If a // tracing system does not know how to handle a particular value type, it // may ignore the tag, but shall not panic. // // Returns a reference to this Span for chaining. SetTag(key string, value interface{}) Span // LogFields is an efficient and type-checked way to record key:value // logging data about a Span, though the programming interface is a little // more verbose than LogKV(). Here's an example: // // span.LogFields( // log.String("event", "soft error"), // log.String("type", "cache timeout"), // log.Int("waited.millis", 1500)) // // Also see Span.FinishWithOptions() and FinishOptions.BulkLogData. LogFields(fields ...log.Field) // LogKV is a concise, readable way to record key:value logging data about // a Span, though unfortunately this also makes it less efficient and less // type-safe than LogFields(). Here's an example: // // span.LogKV( // "event", "soft error", // "type", "cache timeout", // "waited.millis", 1500) // // For LogKV (as opposed to LogFields()), the parameters must appear as // key-value pairs, like // // span.LogKV(key1, val1, key2, val2, key3, val3, ...) // // The keys must all be strings. The values may be strings, numeric types, // bools, Go error instances, or arbitrary structs. // // (Note to implementors: consider the log.InterleavedKVToFields() helper) LogKV(alternatingKeyValues ...interface{}) // SetBaggageItem sets a key:value pair on this Span and its SpanContext // that also propagates to descendants of this Span. // // SetBaggageItem() enables powerful functionality given a full-stack // opentracing integration (e.g., arbitrary application data from a mobile // app can make it, transparently, all the way into the depths of a storage // system), and with it some powerful costs: use this feature with care. // // IMPORTANT NOTE #1: SetBaggageItem() will only propagate baggage items to // *future* causal descendants of the associated Span. // // IMPORTANT NOTE #2: Use this thoughtfully and with care. Every key and // value is copied into every local *and remote* child of the associated // Span, and that can add up to a lot of network and cpu overhead. // // Returns a reference to this Span for chaining. SetBaggageItem(restrictedKey, value string) Span // Gets the value for a baggage item given its key. Returns the empty string // if the value isn't found in this Span. BaggageItem(restrictedKey string) string // Provides access to the Tracer that created this Span. Tracer() Tracer // Deprecated: use LogFields or LogKV LogEvent(event string) // Deprecated: use LogFields or LogKV LogEventWithPayload(event string, payload interface{}) // Deprecated: use LogFields or LogKV Log(data LogData) } // LogRecord is data associated with a single Span log. Every LogRecord // instance must specify at least one Field. type LogRecord struct { Timestamp time.Time Fields []log.Field } // FinishOptions allows Span.FinishWithOptions callers to override the finish // timestamp and provide log data via a bulk interface. type FinishOptions struct { // FinishTime overrides the Span's finish time, or implicitly becomes // time.Now() if FinishTime.IsZero(). // // FinishTime must resolve to a timestamp that's >= the Span's StartTime // (per StartSpanOptions). FinishTime time.Time // LogRecords allows the caller to specify the contents of many LogFields() // calls with a single slice. May be nil. // // None of the LogRecord.Timestamp values may be .IsZero() (i.e., they must // be set explicitly). Also, they must be >= the Span's start timestamp and // <= the FinishTime (or time.Now() if FinishTime.IsZero()). Otherwise the // behavior of FinishWithOptions() is undefined. // // If specified, the caller hands off ownership of LogRecords at // FinishWithOptions() invocation time. // // If specified, the (deprecated) BulkLogData must be nil or empty. LogRecords []LogRecord // BulkLogData is DEPRECATED. BulkLogData []LogData } // LogData is DEPRECATED type LogData struct { Timestamp time.Time Event string Payload interface{} } // ToLogRecord converts a deprecated LogData to a non-deprecated LogRecord func (ld *LogData) ToLogRecord() LogRecord { var literalTimestamp time.Time if ld.Timestamp.IsZero() { literalTimestamp = time.Now() } else { literalTimestamp = ld.Timestamp } rval := LogRecord{ Timestamp: literalTimestamp, } if ld.Payload == nil { rval.Fields = []log.Field{ log.String("event", ld.Event), } } else { rval.Fields = []log.Field{ log.String("event", ld.Event), log.Object("payload", ld.Payload), } } return rval } golang-github-opentracing-opentracing-go-1.2.0/testtracer_test.go000066400000000000000000000074611377553415200252540ustar00rootroot00000000000000package opentracing import ( "strconv" "strings" "time" "github.com/opentracing/opentracing-go/log" ) const testHTTPHeaderPrefix = "testprefix-" // testTracer is a most-noop Tracer implementation that makes it possible for // unittests to verify whether certain methods were / were not called. type testTracer struct{} var fakeIDSource = 1 func nextFakeID() int { fakeIDSource++ return fakeIDSource } type testSpanContext struct { HasParent bool FakeID int } func (n testSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {} type testSpan struct { spanContext testSpanContext OperationName string StartTime time.Time Tags map[string]interface{} } func (n testSpan) Equal(os Span) bool { other, ok := os.(testSpan) if !ok { return false } if n.spanContext != other.spanContext { return false } if n.OperationName != other.OperationName { return false } if !n.StartTime.Equal(other.StartTime) { return false } if len(n.Tags) != len(other.Tags) { return false } for k, v := range n.Tags { if ov, ok := other.Tags[k]; !ok || ov != v { return false } } return true } // testSpan: func (n testSpan) Context() SpanContext { return n.spanContext } func (n testSpan) SetTag(key string, value interface{}) Span { return n } func (n testSpan) Finish() {} func (n testSpan) FinishWithOptions(opts FinishOptions) {} func (n testSpan) LogFields(fields ...log.Field) {} func (n testSpan) LogKV(kvs ...interface{}) {} func (n testSpan) SetOperationName(operationName string) Span { return n } func (n testSpan) Tracer() Tracer { return testTracer{} } func (n testSpan) SetBaggageItem(key, val string) Span { return n } func (n testSpan) BaggageItem(key string) string { return "" } func (n testSpan) LogEvent(event string) {} func (n testSpan) LogEventWithPayload(event string, payload interface{}) {} func (n testSpan) Log(data LogData) {} // StartSpan belongs to the Tracer interface. func (n testTracer) StartSpan(operationName string, opts ...StartSpanOption) Span { sso := StartSpanOptions{} for _, o := range opts { o.Apply(&sso) } return n.startSpanWithOptions(operationName, sso) } func (n testTracer) startSpanWithOptions(name string, opts StartSpanOptions) Span { fakeID := nextFakeID() if len(opts.References) > 0 { fakeID = opts.References[0].ReferencedContext.(testSpanContext).FakeID } return testSpan{ OperationName: name, StartTime: opts.StartTime, Tags: opts.Tags, spanContext: testSpanContext{ HasParent: len(opts.References) > 0, FakeID: fakeID, }, } } // Inject belongs to the Tracer interface. func (n testTracer) Inject(sp SpanContext, format interface{}, carrier interface{}) error { spanContext := sp.(testSpanContext) switch format { case HTTPHeaders, TextMap: carrier.(TextMapWriter).Set(testHTTPHeaderPrefix+"fakeid", strconv.Itoa(spanContext.FakeID)) return nil } return ErrUnsupportedFormat } // Extract belongs to the Tracer interface. func (n testTracer) Extract(format interface{}, carrier interface{}) (SpanContext, error) { switch format { case HTTPHeaders, TextMap: // Just for testing purposes... generally not a worthwhile thing to // propagate. sm := testSpanContext{} err := carrier.(TextMapReader).ForeachKey(func(key, val string) error { switch strings.ToLower(key) { case testHTTPHeaderPrefix + "fakeid": i, err := strconv.Atoi(val) if err != nil { return err } sm.FakeID = i } return nil }) return sm, err } return nil, ErrSpanContextNotFound } golang-github-opentracing-opentracing-go-1.2.0/tracer.go000066400000000000000000000237221377553415200233130ustar00rootroot00000000000000package opentracing import "time" // Tracer is a simple, thin interface for Span creation and SpanContext // propagation. type Tracer interface { // Create, start, and return a new Span with the given `operationName` and // incorporate the given StartSpanOption `opts`. (Note that `opts` borrows // from the "functional options" pattern, per // http://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis) // // A Span with no SpanReference options (e.g., opentracing.ChildOf() or // opentracing.FollowsFrom()) becomes the root of its own trace. // // Examples: // // var tracer opentracing.Tracer = ... // // // The root-span case: // sp := tracer.StartSpan("GetFeed") // // // The vanilla child span case: // sp := tracer.StartSpan( // "GetFeed", // opentracing.ChildOf(parentSpan.Context())) // // // All the bells and whistles: // sp := tracer.StartSpan( // "GetFeed", // opentracing.ChildOf(parentSpan.Context()), // opentracing.Tag{"user_agent", loggedReq.UserAgent}, // opentracing.StartTime(loggedReq.Timestamp), // ) // StartSpan(operationName string, opts ...StartSpanOption) Span // Inject() takes the `sm` SpanContext instance and injects it for // propagation within `carrier`. The actual type of `carrier` depends on // the value of `format`. // // OpenTracing defines a common set of `format` values (see BuiltinFormat), // and each has an expected carrier type. // // Other packages may declare their own `format` values, much like the keys // used by `context.Context` (see https://godoc.org/context#WithValue). // // Example usage (sans error handling): // // carrier := opentracing.HTTPHeadersCarrier(httpReq.Header) // err := tracer.Inject( // span.Context(), // opentracing.HTTPHeaders, // carrier) // // NOTE: All opentracing.Tracer implementations MUST support all // BuiltinFormats. // // Implementations may return opentracing.ErrUnsupportedFormat if `format` // is not supported by (or not known by) the implementation. // // Implementations may return opentracing.ErrInvalidCarrier or any other // implementation-specific error if the format is supported but injection // fails anyway. // // See Tracer.Extract(). Inject(sm SpanContext, format interface{}, carrier interface{}) error // Extract() returns a SpanContext instance given `format` and `carrier`. // // OpenTracing defines a common set of `format` values (see BuiltinFormat), // and each has an expected carrier type. // // Other packages may declare their own `format` values, much like the keys // used by `context.Context` (see // https://godoc.org/golang.org/x/net/context#WithValue). // // Example usage (with StartSpan): // // // carrier := opentracing.HTTPHeadersCarrier(httpReq.Header) // clientContext, err := tracer.Extract(opentracing.HTTPHeaders, carrier) // // // ... assuming the ultimate goal here is to resume the trace with a // // server-side Span: // var serverSpan opentracing.Span // if err == nil { // span = tracer.StartSpan( // rpcMethodName, ext.RPCServerOption(clientContext)) // } else { // span = tracer.StartSpan(rpcMethodName) // } // // // NOTE: All opentracing.Tracer implementations MUST support all // BuiltinFormats. // // Return values: // - A successful Extract returns a SpanContext instance and a nil error // - If there was simply no SpanContext to extract in `carrier`, Extract() // returns (nil, opentracing.ErrSpanContextNotFound) // - If `format` is unsupported or unrecognized, Extract() returns (nil, // opentracing.ErrUnsupportedFormat) // - If there are more fundamental problems with the `carrier` object, // Extract() may return opentracing.ErrInvalidCarrier, // opentracing.ErrSpanContextCorrupted, or implementation-specific // errors. // // See Tracer.Inject(). Extract(format interface{}, carrier interface{}) (SpanContext, error) } // StartSpanOptions allows Tracer.StartSpan() callers and implementors a // mechanism to override the start timestamp, specify Span References, and make // a single Tag or multiple Tags available at Span start time. // // StartSpan() callers should look at the StartSpanOption interface and // implementations available in this package. // // Tracer implementations can convert a slice of `StartSpanOption` instances // into a `StartSpanOptions` struct like so: // // func StartSpan(opName string, opts ...opentracing.StartSpanOption) { // sso := opentracing.StartSpanOptions{} // for _, o := range opts { // o.Apply(&sso) // } // ... // } // type StartSpanOptions struct { // Zero or more causal references to other Spans (via their SpanContext). // If empty, start a "root" Span (i.e., start a new trace). References []SpanReference // StartTime overrides the Span's start time, or implicitly becomes // time.Now() if StartTime.IsZero(). StartTime time.Time // Tags may have zero or more entries; the restrictions on map values are // identical to those for Span.SetTag(). May be nil. // // If specified, the caller hands off ownership of Tags at // StartSpan() invocation time. Tags map[string]interface{} } // StartSpanOption instances (zero or more) may be passed to Tracer.StartSpan. // // StartSpanOption borrows from the "functional options" pattern, per // http://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis type StartSpanOption interface { Apply(*StartSpanOptions) } // SpanReferenceType is an enum type describing different categories of // relationships between two Spans. If Span-2 refers to Span-1, the // SpanReferenceType describes Span-1 from Span-2's perspective. For example, // ChildOfRef means that Span-1 created Span-2. // // NOTE: Span-1 and Span-2 do *not* necessarily depend on each other for // completion; e.g., Span-2 may be part of a background job enqueued by Span-1, // or Span-2 may be sitting in a distributed queue behind Span-1. type SpanReferenceType int const ( // ChildOfRef refers to a parent Span that caused *and* somehow depends // upon the new child Span. Often (but not always), the parent Span cannot // finish until the child Span does. // // An timing diagram for a ChildOfRef that's blocked on the new Span: // // [-Parent Span---------] // [-Child Span----] // // See http://opentracing.io/spec/ // // See opentracing.ChildOf() ChildOfRef SpanReferenceType = iota // FollowsFromRef refers to a parent Span that does not depend in any way // on the result of the new child Span. For instance, one might use // FollowsFromRefs to describe pipeline stages separated by queues, // or a fire-and-forget cache insert at the tail end of a web request. // // A FollowsFromRef Span is part of the same logical trace as the new Span: // i.e., the new Span is somehow caused by the work of its FollowsFromRef. // // All of the following could be valid timing diagrams for children that // "FollowFrom" a parent. // // [-Parent Span-] [-Child Span-] // // // [-Parent Span--] // [-Child Span-] // // // [-Parent Span-] // [-Child Span-] // // See http://opentracing.io/spec/ // // See opentracing.FollowsFrom() FollowsFromRef ) // SpanReference is a StartSpanOption that pairs a SpanReferenceType and a // referenced SpanContext. See the SpanReferenceType documentation for // supported relationships. If SpanReference is created with // ReferencedContext==nil, it has no effect. Thus it allows for a more concise // syntax for starting spans: // // sc, _ := tracer.Extract(someFormat, someCarrier) // span := tracer.StartSpan("operation", opentracing.ChildOf(sc)) // // The `ChildOf(sc)` option above will not panic if sc == nil, it will just // not add the parent span reference to the options. type SpanReference struct { Type SpanReferenceType ReferencedContext SpanContext } // Apply satisfies the StartSpanOption interface. func (r SpanReference) Apply(o *StartSpanOptions) { if r.ReferencedContext != nil { o.References = append(o.References, r) } } // ChildOf returns a StartSpanOption pointing to a dependent parent span. // If sc == nil, the option has no effect. // // See ChildOfRef, SpanReference func ChildOf(sc SpanContext) SpanReference { return SpanReference{ Type: ChildOfRef, ReferencedContext: sc, } } // FollowsFrom returns a StartSpanOption pointing to a parent Span that caused // the child Span but does not directly depend on its result in any way. // If sc == nil, the option has no effect. // // See FollowsFromRef, SpanReference func FollowsFrom(sc SpanContext) SpanReference { return SpanReference{ Type: FollowsFromRef, ReferencedContext: sc, } } // StartTime is a StartSpanOption that sets an explicit start timestamp for the // new Span. type StartTime time.Time // Apply satisfies the StartSpanOption interface. func (t StartTime) Apply(o *StartSpanOptions) { o.StartTime = time.Time(t) } // Tags are a generic map from an arbitrary string key to an opaque value type. // The underlying tracing system is responsible for interpreting and // serializing the values. type Tags map[string]interface{} // Apply satisfies the StartSpanOption interface. func (t Tags) Apply(o *StartSpanOptions) { if o.Tags == nil { o.Tags = make(map[string]interface{}) } for k, v := range t { o.Tags[k] = v } } // Tag may be passed as a StartSpanOption to add a tag to new spans, // or its Set method may be used to apply the tag to an existing Span, // for example: // // tracer.StartSpan("opName", Tag{"Key", value}) // // or // // Tag{"key", value}.Set(span) type Tag struct { Key string Value interface{} } // Apply satisfies the StartSpanOption interface. func (t Tag) Apply(o *StartSpanOptions) { if o.Tags == nil { o.Tags = make(map[string]interface{}) } o.Tags[t.Key] = t.Value } // Set applies the tag to an existing Span. func (t Tag) Set(s Span) { s.SetTag(t.Key, t.Value) }