├── .editorconfig ├── .github ├── release.yml └── workflows │ ├── ci.yml │ ├── codeql.yml │ ├── dependency-review.yml │ ├── labeler.yml │ └── lint.yml ├── .golangci.yml ├── LICENSE ├── Makefile ├── README.md ├── amqptracer ├── doc.go ├── propagation.go ├── propagation_test.go ├── testtracer_test.go ├── tracer.go └── tracer_test.go ├── go.mod ├── go.sum └── renovate.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | indent_size = 4 9 | indent_style = tab 10 | 11 | [*.{md,yml,yaml}] 12 | indent_size = 2 13 | indent_style = space 14 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - skip changelog 5 | categories: 6 | - title: 💣 Breaking Changes 7 | labels: 8 | - change 9 | - title: 🚀 Features 10 | labels: 11 | - enhancement 12 | - title: 🐛 Bug Fixes 13 | labels: 14 | - bug 15 | - title: 🧪 Tests 16 | labels: 17 | - tests 18 | - title: 🔨 Maintenance 19 | labels: 20 | - chore 21 | - title: 📝 Documentation 22 | labels: 23 | - documentation 24 | - title: ⬆️ Dependencies 25 | labels: 26 | - dependencies 27 | - title: Other Changes 28 | labels: 29 | - "*" 30 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - "v[0-9]+.[0-9]+.[0-9]+*" 9 | pull_request: 10 | branches: 11 | - master 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-24.04 16 | steps: 17 | - name: Checkout Repository 18 | uses: actions/checkout@v4 19 | 20 | - name: Setup Golang Environment 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version: stable 24 | 25 | - name: Build 26 | run: go build -v ./... 27 | 28 | test: 29 | runs-on: ubuntu-24.04 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | go-version: [ 34 | oldstable, 35 | stable, 36 | ] 37 | steps: 38 | - name: Checkout Repository 39 | uses: actions/checkout@v4 40 | 41 | - name: Setup Golang Environment 42 | uses: actions/setup-go@v5 43 | with: 44 | go-version: ${{ matrix.go-version }} 45 | 46 | - name: Run Unit Tests 47 | run: go test -v -cover -race -shuffle=on ./... 48 | 49 | draft-release: 50 | runs-on: ubuntu-24.04 51 | if: github.event_name != 'pull_request' 52 | steps: 53 | - name: Checkout Repository 54 | uses: actions/checkout@v4 55 | 56 | - name: Create/Update Draft Release 57 | uses: lucacome/draft-release@v1.2.1 58 | with: 59 | minor-label: "enhancement" 60 | major-label: "change" 61 | collapse-after: 20 62 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | schedule: 11 | - cron: '32 21 * * 0' 12 | 13 | concurrency: 14 | group: ${{ github.ref_name }}-codeql 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze (${{ matrix.language }}) 20 | runs-on: ubuntu-24.04 21 | permissions: 22 | # required for all workflows 23 | security-events: write 24 | 25 | # required to fetch internal or private CodeQL packs 26 | packages: read 27 | 28 | # only required for workflows in private repositories 29 | actions: read 30 | contents: read 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | include: 36 | - language: go 37 | build-mode: autobuild 38 | steps: 39 | - name: Checkout repository 40 | uses: actions/checkout@v4 41 | 42 | # Initializes the CodeQL tools for scanning. 43 | - name: Initialize CodeQL 44 | uses: github/codeql-action/init@v3 45 | with: 46 | languages: ${{ matrix.language }} 47 | build-mode: ${{ matrix.build-mode }} 48 | queries: security-and-quality 49 | 50 | - name: Perform CodeQL Analysis 51 | uses: github/codeql-action/analyze@v3 52 | with: 53 | category: "/language:${{matrix.language}}" 54 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | name: Dependency review 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | 7 | permissions: 8 | contents: read 9 | # Write permissions for pull-requests are required for using the `comment-summary-in-pr` option, comment out if you aren't using this option 10 | pull-requests: write 11 | 12 | jobs: 13 | dependency-review: 14 | runs-on: ubuntu-24.04 15 | steps: 16 | - name: Checkout Repository 17 | uses: actions/checkout@v4 18 | 19 | - name: Dependency Review 20 | uses: actions/dependency-review-action@v4 21 | with: 22 | config-file: "opentracing-contrib/common/dependency-review-config.yml@main" 23 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Labeler 2 | on: 3 | - pull_request_target 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | triage: 10 | permissions: 11 | contents: read 12 | pull-requests: write 13 | runs-on: ubuntu-24.04 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | sparse-checkout: | 18 | labeler.yml 19 | sparse-checkout-cone-mode: false 20 | repository: opentracing-contrib/common 21 | 22 | - uses: actions/labeler@v5 23 | with: 24 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 25 | sync-labels: true 26 | configuration-path: labeler.yml 27 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | defaults: 12 | run: 13 | shell: bash 14 | 15 | concurrency: 16 | group: ${{ github.ref_name }}-lint 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | lint: 21 | name: Go Lint 22 | runs-on: ubuntu-24.04 23 | steps: 24 | - name: Checkout Repository 25 | uses: actions/checkout@v4 26 | 27 | - name: Setup Golang Environment 28 | uses: actions/setup-go@v5 29 | with: 30 | go-version: stable 31 | 32 | - name: Lint Go 33 | uses: golangci/golangci-lint-action@v6 34 | 35 | actionlint: 36 | name: Actionlint 37 | runs-on: ubuntu-24.04 38 | steps: 39 | - name: Checkout Repository 40 | uses: actions/checkout@v4 41 | 42 | - name: Lint Actions 43 | uses: reviewdog/action-actionlint@v1 44 | with: 45 | actionlint_flags: -shellcheck "" 46 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | misspell: 3 | locale: US 4 | revive: 5 | ignore-generated-header: true 6 | rules: 7 | - name: blank-imports 8 | - name: context-as-argument 9 | - name: context-keys-type 10 | - name: dot-imports 11 | - name: empty-block 12 | - name: error-naming 13 | - name: error-return 14 | - name: error-strings 15 | - name: errorf 16 | - name: exported 17 | - name: increment-decrement 18 | - name: indent-error-flow 19 | - name: package-comments 20 | - name: range 21 | - name: receiver-naming 22 | - name: redefines-builtin-id 23 | - name: superfluous-else 24 | - name: time-naming 25 | - name: unexported-return 26 | - name: unreachable-code 27 | - name: var-declaration 28 | - name: var-naming 29 | govet: 30 | enable-all: true 31 | linters: 32 | enable: 33 | - asasalint 34 | - asciicheck 35 | - bidichk 36 | - contextcheck 37 | - copyloopvar 38 | - dupword 39 | - durationcheck 40 | - errcheck 41 | - errchkjson 42 | - errname 43 | - errorlint 44 | - fatcontext 45 | - forcetypeassert 46 | - gocheckcompilerdirectives 47 | - gochecksumtype 48 | - gocritic 49 | - godot 50 | - gofmt 51 | - gofumpt 52 | - goimports 53 | - gosec 54 | - gosimple 55 | - gosmopolitan 56 | - govet 57 | - ineffassign 58 | - intrange 59 | - makezero 60 | - misspell 61 | - musttag 62 | - nilerr 63 | - noctx 64 | - nolintlint 65 | - perfsprint 66 | - prealloc 67 | - predeclared 68 | - reassign 69 | - revive 70 | - staticcheck 71 | - stylecheck 72 | - tagalign 73 | - thelper 74 | - tparallel 75 | - typecheck 76 | - unconvert 77 | - unparam 78 | - unused 79 | - usestdlibvars 80 | - usetesting 81 | - wastedassign 82 | - whitespace 83 | # - wrapcheck 84 | disable-all: true 85 | issues: 86 | max-issues-per-linter: 0 87 | max-same-issues: 0 88 | run: 89 | timeout: 5m 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 OpenTracing API Contributions 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGES := ./amqptracer/... 2 | 3 | .DEFAULT_GOAL := test-and-lint 4 | 5 | .PHONE: test-and-lint 6 | 7 | test-and-lint: test lint 8 | 9 | .PHONY: test 10 | test: 11 | go test -v -cover ./... 12 | 13 | cover: 14 | @rm -rf cover-all.out 15 | $(foreach pkg, $(PACKAGES), $(MAKE) cover-pkg PKG=$(pkg) || true;) 16 | @grep mode: cover.out > coverage.out 17 | @cat cover-all.out >> coverage.out 18 | go tool cover -html=coverage.out -o cover.html 19 | @rm -rf cover.out cover-all.out coverage.out 20 | 21 | cover-pkg: 22 | go test -coverprofile cover.out $(PKG) 23 | @grep -v mode: cover.out >> cover-all.out 24 | 25 | .PHONY: lint 26 | lint: 27 | go fmt ./... 28 | golint ./... 29 | @# Run again with magic to exit non-zero if golint outputs anything. 30 | @! (golint ./... | read dummy) 31 | go vet ./... 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-amqp 2 | 3 | [![CI](https://github.com/opentracing-contrib/go-amqp/actions/workflows/ci.yml/badge.svg)](https://github.com/opentracing-contrib/go-amqp/actions/workflows/ci.yml) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/opentracing-contrib/go-amqp)](https://goreportcard.com/report/github.com/opentracing-contrib/go-amqp) 5 | ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/opentracing-contrib/go-amqp) 6 | [![GoDoc]](http://godoc.org/github.com/opentracing-contrib/go-amqp/amqptracer) 7 | 8 | [AMQP] instrumentation in Go 9 | 10 | For documentation on the packages, [check godoc]. 11 | 12 | **The APIs in the various packages are experimental and may change in 13 | the future. You should vendor them to avoid spurious breakage.** 14 | 15 | ## Packages 16 | 17 | Instrumentation is provided for the following packages, with the 18 | following caveats: 19 | 20 | - **github.com/streadway/amqp**: Client and server instrumentation. *Only supported 21 | with Go 1.7 and later.* 22 | 23 | ## Required Reading 24 | 25 | In order to understand the [AMQP] instrumentation in Go, one must first 26 | be familiar with the [OpenTracing project] and [terminology] more 27 | specifically. And also, [OpenTracing API for Go] contains enough examples 28 | to get started with OpenTracing in Go. 29 | 30 | ## API overview for the AMQP instrumentation 31 | 32 | Here are the example serialization and deserialization of the `opentracing` 33 | `SapnContext` over the AMQP broker so that we can visualize the tracing 34 | between the producers and the consumers. 35 | 36 | ### Serializing to the wire 37 | 38 | ```go 39 | func PublishMessage( 40 | ctx context.Context, 41 | ch *amqp.Channel, 42 | immediate bool, 43 | msg *amqp.Publishing, 44 | ) error { 45 | sp := opentracing.SpanFromContext(ctx) 46 | defer sp.Finish() 47 | 48 | // Inject the span context into the AMQP header. 49 | if err := amqptracer.Inject(sp, msg.Headers); err != nil { 50 | return err 51 | } 52 | 53 | // Publish the message with the span context. 54 | return ch.Publish(exchange, key, mandatory, immediate, msg) 55 | } 56 | ``` 57 | 58 | ### Deserializing from the wire 59 | 60 | ```go 61 | func ConsumeMessage(ctx context.Context, msg *amqp.Delivery) error { 62 | // Extract the span context out of the AMQP header. 63 | spCtx, _ := amqptracer.Extract(msg.Headers) 64 | sp := opentracing.StartSpan( 65 | "ConsumeMessage", 66 | opentracing.FollowsFrom(spCtx), 67 | ) 68 | defer sp.Finish() 69 | 70 | // Update the context with the span for the subsequent reference. 71 | ctx = opentracing.ContextWithSpan(ctx, sp) 72 | 73 | // Actual message processing. 74 | return ProcessMessage(ctx, msg) 75 | } 76 | ``` 77 | 78 | [OpenTracing project]: http://opentracing.io 79 | [terminology]: http://opentracing.io/documentation/pages/spec.html 80 | [OpenTracing API for Go]: https://github.com/opentracing/opentracing-go 81 | [AMQP]: https://github.com/streadway/amqp 82 | [GoDoc]: https://godoc.org/github.com/opentracing-contrib/go-amqp/amqptracer?status.svg 83 | [check godoc]: https://godoc.org/github.com/opentracing-contrib/go-amqp/amqptracer 84 | -------------------------------------------------------------------------------- /amqptracer/doc.go: -------------------------------------------------------------------------------- 1 | // Package amqptracer provides OpenTracing instrumentation for the 2 | // github.com/streadway/amqp package. 3 | package amqptracer 4 | -------------------------------------------------------------------------------- /amqptracer/propagation.go: -------------------------------------------------------------------------------- 1 | package amqptracer 2 | 3 | // amqpHeadersCarrier satisfies both TextMapWriter and TextMapReader. 4 | // 5 | // Example usage for server side: 6 | // 7 | // carrier := amqpHeadersCarrier(amqp.Table) 8 | // clientContext, err := tracer.Extract( 9 | // opentracing.TextMap, 10 | // carrier) 11 | // 12 | // Example usage for client side: 13 | // 14 | // carrier := amqpHeadersCarrier(amqp.Table) 15 | // err := tracer.Inject( 16 | // span.Context(), 17 | // opentracing.TextMap, 18 | // carrier) 19 | type amqpHeadersCarrier map[string]interface{} 20 | 21 | // ForeachKey conforms to the TextMapReader interface. 22 | func (c amqpHeadersCarrier) ForeachKey(handler func(key, val string) error) error { 23 | for k, val := range c { 24 | v, ok := val.(string) 25 | if !ok { 26 | continue 27 | } 28 | if err := handler(k, v); err != nil { 29 | return err 30 | } 31 | } 32 | return nil 33 | } 34 | 35 | // Set implements Set() of opentracing.TextMapWriter. 36 | func (c amqpHeadersCarrier) Set(key, val string) { 37 | c[key] = val 38 | } 39 | -------------------------------------------------------------------------------- /amqptracer/propagation_test.go: -------------------------------------------------------------------------------- 1 | package amqptracer 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | 7 | opentracing "github.com/opentracing/opentracing-go" 8 | ) 9 | 10 | func TestAMQPHeaderInject(t *testing.T) { 11 | h := map[string]interface{}{} 12 | h["NotOT"] = "blah" 13 | h["opname"] = "AlsoNotOT" 14 | tracer := testTracer{} 15 | span := tracer.StartSpan("someSpan") 16 | 17 | spanCtx, ok := span.Context().(testSpanContext) 18 | if !ok { 19 | t.Fatalf("Expected span.Context() to be of type testSpanContext") 20 | } 21 | fakeID := spanCtx.FakeID 22 | 23 | // Use amqpHeadersCarrier to wrap around `h`. 24 | carrier := amqpHeadersCarrier(h) 25 | if err := span.Tracer().Inject(span.Context(), opentracing.TextMap, carrier); err != nil { 26 | t.Fatal(err) 27 | } 28 | 29 | if len(h) != 3 { 30 | t.Errorf("Unexpected header length: %v", len(h)) 31 | } 32 | // The prefix comes from just above; the suffix comes from 33 | // testTracer.Inject(). 34 | if h["testprefix-fakeid"] != strconv.Itoa(fakeID) { 35 | t.Errorf("Could not find fakeid at expected key") 36 | } 37 | } 38 | 39 | func TestAMQPHeaderExtract(t *testing.T) { 40 | h := map[string]interface{}{} 41 | h["NotOT"] = "blah" 42 | h["opname"] = "AlsoNotOT" 43 | h["testprefix-fakeid"] = "42" 44 | tracer := testTracer{} 45 | 46 | // Use amqpHeadersCarrier to wrap around `h`. 47 | carrier := amqpHeadersCarrier(h) 48 | spanContext, err := tracer.Extract(opentracing.TextMap, carrier) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | 53 | testContext, ok := spanContext.(testSpanContext) 54 | if !ok { 55 | t.Fatalf("Expected spanContext to be of type testSpanContext") 56 | } 57 | if testContext.FakeID != 42 { 58 | t.Errorf("Failed to read testprefix-fakeid correctly") 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /amqptracer/testtracer_test.go: -------------------------------------------------------------------------------- 1 | package amqptracer 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | "time" 7 | 8 | opentracing "github.com/opentracing/opentracing-go" 9 | "github.com/opentracing/opentracing-go/log" 10 | ) 11 | 12 | const testHTTPHeaderPrefix = "testprefix-" 13 | 14 | // testTracer is a most-noop Tracer implementation that makes it possible for 15 | // unittests to verify whether certain methods were / were not called. 16 | type testTracer struct{} 17 | 18 | var fakeIDSource = 1 19 | 20 | func nextFakeID() int { 21 | fakeIDSource++ 22 | return fakeIDSource 23 | } 24 | 25 | type testSpanContext struct { 26 | HasParent bool 27 | FakeID int 28 | } 29 | 30 | func (n testSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {} 31 | 32 | type testSpan struct { 33 | StartTime time.Time 34 | Tags map[string]interface{} 35 | OperationName string 36 | spanContext testSpanContext 37 | } 38 | 39 | func (n testSpan) Equal(os opentracing.Span) bool { 40 | other, ok := os.(testSpan) 41 | if !ok { 42 | return false 43 | } 44 | if n.spanContext != other.spanContext { 45 | return false 46 | } 47 | if n.OperationName != other.OperationName { 48 | return false 49 | } 50 | if !n.StartTime.Equal(other.StartTime) { 51 | return false 52 | } 53 | if len(n.Tags) != len(other.Tags) { 54 | return false 55 | } 56 | 57 | for k, v := range n.Tags { 58 | if ov, ok := other.Tags[k]; !ok || ov != v { 59 | return false 60 | } 61 | } 62 | 63 | return true 64 | } 65 | 66 | func (n testSpan) Context() opentracing.SpanContext { return n.spanContext } 67 | func (n testSpan) SetTag(key string, value interface{}) opentracing.Span { return n } 68 | func (n testSpan) Finish() {} 69 | func (n testSpan) FinishWithOptions(opts opentracing.FinishOptions) {} 70 | func (n testSpan) LogFields(fields ...log.Field) {} 71 | func (n testSpan) LogKV(kvs ...interface{}) {} 72 | func (n testSpan) SetOperationName(operationName string) opentracing.Span { return n } 73 | func (n testSpan) Tracer() opentracing.Tracer { return testTracer{} } 74 | func (n testSpan) SetBaggageItem(key, val string) opentracing.Span { return n } 75 | func (n testSpan) BaggageItem(key string) string { return "" } 76 | func (n testSpan) LogEvent(event string) {} 77 | func (n testSpan) LogEventWithPayload(event string, payload interface{}) {} 78 | func (n testSpan) Log(data opentracing.LogData) {} 79 | 80 | // StartSpan belongs to the Tracer interface. 81 | func (n testTracer) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span { 82 | sso := opentracing.StartSpanOptions{} 83 | for _, o := range opts { 84 | o.Apply(&sso) 85 | } 86 | return n.startSpanWithOptions(operationName, sso) 87 | } 88 | 89 | func (n testTracer) startSpanWithOptions(name string, opts opentracing.StartSpanOptions) opentracing.Span { 90 | fakeID := nextFakeID() 91 | if len(opts.References) > 0 { 92 | if ctx, ok := opts.References[0].ReferencedContext.(testSpanContext); ok { 93 | fakeID = ctx.FakeID 94 | } 95 | } 96 | 97 | return testSpan{ 98 | OperationName: name, 99 | StartTime: opts.StartTime, 100 | Tags: opts.Tags, 101 | spanContext: testSpanContext{ 102 | HasParent: len(opts.References) > 0, 103 | FakeID: fakeID, 104 | }, 105 | } 106 | } 107 | 108 | // Inject belongs to the Tracer interface. 109 | func (n testTracer) Inject(sp opentracing.SpanContext, format interface{}, carrier interface{}) error { 110 | spanContext, ok := sp.(testSpanContext) 111 | if !ok { 112 | return opentracing.ErrInvalidSpanContext 113 | } 114 | 115 | switch format { 116 | case opentracing.HTTPHeaders, opentracing.TextMap: 117 | writer, ok := carrier.(opentracing.TextMapWriter) 118 | if !ok { 119 | return opentracing.ErrInvalidCarrier 120 | } 121 | writer.Set(testHTTPHeaderPrefix+"fakeid", strconv.Itoa(spanContext.FakeID)) 122 | return nil 123 | } 124 | return opentracing.ErrUnsupportedFormat 125 | } 126 | 127 | // Extract belongs to the Tracer interface. 128 | func (n testTracer) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) { 129 | if format == opentracing.HTTPHeaders || format == opentracing.TextMap { 130 | // Just for testing purposes... generally not a worthwhile thing to 131 | // propagate. 132 | sm := testSpanContext{} 133 | reader, ok := carrier.(opentracing.TextMapReader) 134 | if !ok { 135 | return nil, opentracing.ErrInvalidCarrier 136 | } 137 | 138 | err := reader.ForeachKey(func(key, val string) error { 139 | lowerKey := strings.ToLower(key) 140 | if lowerKey == testHTTPHeaderPrefix+"fakeid" { 141 | i, err := strconv.Atoi(val) 142 | if err != nil { 143 | return err 144 | } 145 | sm.FakeID = i 146 | } 147 | return nil 148 | }) 149 | return sm, err 150 | } 151 | return nil, opentracing.ErrSpanContextNotFound 152 | } 153 | -------------------------------------------------------------------------------- /amqptracer/tracer.go: -------------------------------------------------------------------------------- 1 | package amqptracer 2 | 3 | import ( 4 | opentracing "github.com/opentracing/opentracing-go" 5 | "github.com/streadway/amqp" 6 | ) 7 | 8 | // Inject injects the span context into the AMQP header. 9 | // 10 | // Example: 11 | // 12 | // func PublishMessage( 13 | // ctx context.Context, 14 | // ch *amqp.Channel, 15 | // exchange, key string, 16 | // mandatory, immediate bool, 17 | // msg *amqp.Publishing, 18 | // ) error { 19 | // sp := opentracing.SpanFromContext(ctx) 20 | // defer sp.Finish() 21 | // 22 | // // Inject the span context into the AMQP header. 23 | // if err := amqptracer.Inject(sp, msg.Headers); err != nil { 24 | // return err 25 | // } 26 | // 27 | // // Publish the message with the span context. 28 | // return ch.Publish(exchange, key, mandatory, immediate, msg) 29 | // } 30 | func Inject(span opentracing.Span, hdrs amqp.Table) error { 31 | c := amqpHeadersCarrier(hdrs) 32 | return span.Tracer().Inject(span.Context(), opentracing.TextMap, c) 33 | } 34 | 35 | // Extract extracts the span context out of the AMQP header. 36 | // 37 | // Example: 38 | // 39 | // func ConsumeMessage(ctx context.Context, msg *amqp.Delivery) error { 40 | // // Extract the span context out of the AMQP header. 41 | // spCtx, _ := amqptracer.Extract(msg.Headers) 42 | // sp := opentracing.StartSpan( 43 | // "ConsumeMessage", 44 | // opentracing.FollowsFrom(spCtx), 45 | // ) 46 | // defer sp.Finish() 47 | // 48 | // // Update the context with the span for the subsequent reference. 49 | // ctx = opentracing.ContextWithSpan(ctx, sp) 50 | // 51 | // // Actual message processing. 52 | // return ProcessMessage(ctx, msg) 53 | // } 54 | func Extract(hdrs amqp.Table) (opentracing.SpanContext, error) { 55 | c := amqpHeadersCarrier(hdrs) 56 | return opentracing.GlobalTracer().Extract(opentracing.TextMap, c) 57 | } 58 | -------------------------------------------------------------------------------- /amqptracer/tracer_test.go: -------------------------------------------------------------------------------- 1 | package amqptracer 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | 7 | opentracing "github.com/opentracing/opentracing-go" 8 | ) 9 | 10 | func TestInject(t *testing.T) { 11 | h := map[string]interface{}{} 12 | h["NotOT"] = "blah" 13 | h["opname"] = "AlsoNotOT" 14 | tracer := testTracer{} 15 | sp := tracer.StartSpan("someSpan") 16 | 17 | spanCtx, ok := sp.Context().(testSpanContext) 18 | if !ok { 19 | t.Fatalf("Expected sp.Context() to be of type testSpanContext") 20 | } 21 | fakeID := spanCtx.FakeID 22 | 23 | // Inject the tracing context to the AMQP header. 24 | if err := Inject(sp, h); err != nil { 25 | t.Fatal(err) 26 | } 27 | 28 | if len(h) != 3 { 29 | t.Errorf("Unexpected header length: %v", len(h)) 30 | } 31 | // The prefix comes from just above; the suffix comes from 32 | // testTracer.Inject(). 33 | if h["testprefix-fakeid"] != strconv.Itoa(fakeID) { 34 | t.Errorf("Could not find fakeid at expected key") 35 | } 36 | } 37 | 38 | func TestExtract(t *testing.T) { 39 | h := map[string]interface{}{} 40 | h["NotOT"] = "blah" 41 | h["opname"] = "AlsoNotOT" 42 | h["testprefix-fakeid"] = "42" 43 | 44 | // Set the testTracer as the global tracer. 45 | opentracing.SetGlobalTracer(testTracer{}) 46 | 47 | // Extract the tracing span out from the AMQP header. 48 | ctx, err := Extract(h) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | 53 | testCtx, ok := ctx.(testSpanContext) 54 | if !ok { 55 | t.Fatalf("Expected ctx to be of type testSpanContext") 56 | } 57 | if testCtx.FakeID != 42 { 58 | t.Errorf("Failed to read testprefix-fakeid correctly") 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/opentracing-contrib/go-amqp 2 | 3 | go 1.23.3 4 | 5 | require ( 6 | github.com/opentracing/opentracing-go v1.2.0 7 | github.com/streadway/amqp v1.1.0 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 4 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/streadway/amqp v1.1.0 h1:py12iX8XSyI7aN/3dUT8DFIDJazNJsVJdxNVEpnQTZM= 8 | github.com/streadway/amqp v1.1.0/go.mod h1:WYSrTEYHOXHd0nwFeUXAe2G2hRnQT+deZJJf88uS9Bg= 9 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 10 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 11 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 12 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "github>opentracing-contrib/common", 5 | "schedule:weekly" 6 | ] 7 | } 8 | --------------------------------------------------------------------------------