├── .github ├── CODEOWNERS ├── PULL_REQUEST_TEMPLATE.md ├── stale.yml └── workflows │ ├── IntegrationTesting.yml │ ├── continuous-monitoring.yml │ ├── ecr-publish.yml │ ├── release.yml │ ├── reviewdog.yml │ └── test.yml ├── .gitignore ├── .golangci.yaml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── LICENSE ├── NOTICE.txt ├── README.md ├── awsplugins ├── beanstalk │ └── beanstalk.go ├── ec2 │ ├── ec2.go │ └── ec2_test.go └── ecs │ └── ecs.go ├── benchmark ├── README.md └── results │ └── 1.0.0.md ├── daemoncfg ├── daemon_config.go └── daemon_config_test.go ├── go.mod ├── go.sum ├── header ├── header.go └── header_test.go ├── images └── example.png ├── instrumentation └── awsv2 │ ├── awsv2.go │ └── awsv2_test.go ├── integration-tests └── distributioncheck │ ├── go.mod │ ├── go.sum │ └── sanity_test.go ├── internal ├── logger │ ├── logger.go │ └── logger_test.go └── plugins │ └── plugin.go ├── lambda ├── sqs_message_helper.go └── sqs_message_helper_test.go ├── makefile ├── pattern ├── search_pattern.go └── search_pattern_test.go ├── resources ├── AWSWhitelist.json ├── DefaultSamplingRules.json ├── ExampleSamplingRules.json └── bindata.go ├── sample-apps ├── LICENSE └── http-server │ ├── Dockerfile │ ├── application.go │ ├── go.mod │ └── go.sum ├── strategy ├── ctxmissing │ ├── context_missing.go │ ├── ctxmissing_test.go │ └── default_context_missing.go ├── exception │ ├── default_exception_formatting_strategy.go │ ├── exception_formatting_strategy.go │ ├── exception_test.go │ └── xray_error.go └── sampling │ ├── centralized.go │ ├── centralized_sampling_rule_manifest.go │ ├── centralized_sampling_rule_manifest_test.go │ ├── centralized_test.go │ ├── localized.go │ ├── proxy.go │ ├── proxy_test.go │ ├── reservoir.go │ ├── reservoir_test.go │ ├── sampling_attributes.go │ ├── sampling_rule.go │ ├── sampling_rule_manifest.go │ ├── sampling_rule_test.go │ ├── sampling_strategy.go │ ├── sampling_test.go │ ├── testdata │ ├── rule-v1-contains-host.json │ ├── rule-v1-invalid.json │ ├── rule-v1-sampling.json │ ├── rule-v2-contains-service-name.json │ └── rule-v2-sampling.json │ └── xray_service_api.go ├── terraform ├── eb.tf ├── fixtures.us-west-2.tfvars └── variables.tf ├── utils ├── awserr │ ├── error.go │ └── types.go ├── clock.go ├── mock_clock.go ├── mock_rand.go ├── rand.go └── timer.go ├── xray ├── capture.go ├── capture_test.go ├── client.go ├── client_test.go ├── config.go ├── config_test.go ├── context.go ├── context_test.go ├── default_emitter.go ├── default_emitter_test.go ├── default_streaming_strategy.go ├── default_streaming_strategy_test.go ├── emitter.go ├── fasthttp.go ├── fasthttp_test.go ├── grpc.go ├── grpc_test.go ├── handler.go ├── handler_go113_test.go ├── handler_test.go ├── httptrace.go ├── lambda.go ├── lambda_test.go ├── response_capturer.go ├── segment.go ├── segment_model.go ├── segment_test.go ├── sql_context.go ├── sql_go110.go ├── sql_go110_test.go ├── sql_go111_test.go ├── sql_prego111_test.go ├── sqlcontext_test.go ├── streaming_strategy.go └── util_test.go └── xraylog └── xray_log.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | ##################################################### 2 | # 3 | # List of approvers for this repository 4 | # 5 | ##################################################### 6 | # 7 | # Learn about CODEOWNERS file format: 8 | # https://help.github.com/en/articles/about-code-owners 9 | # 10 | 11 | * @aws/aws-x-ray 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 30 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Limit to only `issues` or `pulls` 6 | only: issues 7 | # Issues with these labels will never be considered stale 8 | exemptLabels: 9 | - pinned 10 | - enhancement 11 | - bug 12 | - feature-request 13 | - help wanted 14 | - work-in-progress 15 | - pending release 16 | # Label to use when marking an issue as stale 17 | staleLabel: stale 18 | # Comment to post when marking an issue as stale. Set to `false` to disable 19 | markComment: > 20 | This issue has been automatically marked as stale because it has not had 21 | recent activity. It will be closed if no further activity occurs in next 7 days. Thank you 22 | for your contributions. 23 | # Comment to post when closing a stale issue. Set to `false` to disable 24 | closeComment: false 25 | -------------------------------------------------------------------------------- /.github/workflows/IntegrationTesting.yml: -------------------------------------------------------------------------------- 1 | name: Integration Testing 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - master 7 | 8 | permissions: 9 | id-token: write 10 | contents: read 11 | 12 | jobs: 13 | package_SDK: 14 | name: Build X-Ray Go SDK With Sample App 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout X-Ray Go SDK 19 | uses: actions/checkout@v2 20 | 21 | - name: Create a directory 22 | run: mkdir sample-apps/http-server/aws-xray-sdk-go 23 | 24 | - name: Copy X-Ray SDK to deployment package with Sample App 25 | run: rsync -r * sample-apps/http-server/aws-xray-sdk-go --exclude sample-apps/ 26 | 27 | - name: The application.go file must be at the working directory level in EB Go. We need to change the redirection to the folder we copied in the previous step. 28 | run: sed -i 's|replace github.com/aws/aws-xray-sdk-go/v2 => ../../|replace github.com/aws/aws-xray-sdk-go/v2 => ./aws-xray-sdk-go|g' go.mod 29 | working-directory: ./sample-apps/http-server 30 | 31 | - name: Zip up the deployment package 32 | run: zip -r deploy.zip . -x '*.git*' 33 | working-directory: ./sample-apps/http-server 34 | 35 | - name: Upload WebApp with X-Ray SDK build artifact 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: deployment-package 39 | path: sample-apps/http-server/deploy.zip 40 | 41 | deploy_WebApp: 42 | name: Deploy X-Ray Instrumented Web App 43 | needs: package_SDK 44 | runs-on: ubuntu-latest 45 | 46 | steps: 47 | - uses: actions/checkout@v2 48 | 49 | - name: Download terraform state artifact 50 | uses: actions/download-artifact@v4 51 | with: 52 | name: deployment-package 53 | 54 | - name: Copy deployment package to terraform directory 55 | run: cp deploy.zip ./terraform 56 | 57 | - name: Configure AWS Credentials 58 | uses: aws-actions/configure-aws-credentials@v4 59 | with: 60 | role-to-assume: ${{ secrets.AWS_INTEG_TEST_ROLE_ARN }} 61 | aws-region: us-west-2 62 | 63 | - name: Setup Terraform 64 | uses: hashicorp/setup-terraform@v1 65 | 66 | - name: Terraform Init 67 | run: terraform init 68 | working-directory: ./terraform 69 | 70 | - name: Terraform Validate 71 | run: terraform validate -no-color 72 | working-directory: ./terraform 73 | 74 | - name: Terraform Plan 75 | run: terraform plan -var-file="fixtures.us-west-2.tfvars" -no-color 76 | env: 77 | TF_VAR_resource_prefix: '${{ github.run_id }}-${{ github.run_number }}' 78 | continue-on-error: true 79 | working-directory: ./terraform 80 | 81 | - name: Terraform Apply 82 | run: terraform apply -var-file="fixtures.us-west-2.tfvars" -auto-approve 83 | env: 84 | TF_VAR_resource_prefix: '${{ github.run_id }}-${{ github.run_number }}' 85 | working-directory: ./terraform 86 | 87 | - name: Upload terraform state files for destorying resources 88 | uses: actions/upload-artifact@v4 89 | with: 90 | name: terraform-state-artifact 91 | path: ./terraform 92 | 93 | test_WebApp: 94 | name: Run testing suite 95 | needs: deploy_WebApp 96 | runs-on: ubuntu-latest 97 | 98 | steps: 99 | - uses: actions/setup-java@v1 100 | with: 101 | java-version: 14 102 | 103 | - name: Configure AWS Credentials 104 | uses: aws-actions/configure-aws-credentials@v4 105 | with: 106 | role-to-assume: ${{ secrets.AWS_INTEG_TEST_ROLE_ARN }} 107 | aws-region: us-west-2 108 | 109 | - name: Checkout test framework 110 | uses: actions/checkout@v2 111 | with: 112 | repository: aws-observability/aws-otel-test-framework 113 | ref: terraform 114 | 115 | - name: Run testing suite 116 | run: ./gradlew :validator:run --args='-c default-xray-trace-validation.yml --endpoint http://${{ github.run_id }}-${{ github.run_number }}-eb-app-env.us-west-2.elasticbeanstalk.com' 117 | 118 | cleanup: 119 | name: Resource tear down 120 | needs: test_WebApp 121 | if: always() 122 | runs-on: ubuntu-latest 123 | 124 | steps: 125 | - name: Download terraform state artifact 126 | uses: actions/download-artifact@v4 127 | with: 128 | name: terraform-state-artifact 129 | 130 | - name: Configure AWS Credentials 131 | uses: aws-actions/configure-aws-credentials@v4 132 | with: 133 | role-to-assume: ${{ secrets.AWS_INTEG_TEST_ROLE_ARN }} 134 | aws-region: us-west-2 135 | 136 | - name: Setup Terraform 137 | uses: hashicorp/setup-terraform@v1 138 | 139 | - name: Terraform Init 140 | run: terraform init 141 | 142 | - name: set permissions to terraform plugins 143 | run: chmod -R a+x .terraform/* 144 | 145 | - name: Destroy resources 146 | run: terraform destroy -state="terraform.tfstate" -var-file="fixtures.us-west-2.tfvars" -auto-approve 147 | env: 148 | TF_VAR_resource_prefix: '${{ github.run_id }}-${{ github.run_number }}' 149 | -------------------------------------------------------------------------------- /.github/workflows/continuous-monitoring.yml: -------------------------------------------------------------------------------- 1 | name: Continuous monitoring of distribution channels 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: '*/10 * * * *' 6 | 7 | permissions: 8 | id-token: write 9 | contents: read 10 | 11 | jobs: 12 | smoke-tests: 13 | name: Run smoke tests 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout Repository 17 | uses: actions/checkout@v2 18 | 19 | - name: Configure AWS Credentials 20 | uses: aws-actions/configure-aws-credentials@v4 21 | with: 22 | role-to-assume: ${{ secrets.AWS_INTEG_TEST_ROLE_ARN }} 23 | aws-region: us-east-1 24 | 25 | - uses: actions/setup-go@v2 26 | with: 27 | go-version: '^1.16' 28 | 29 | - name: Run distribution test 30 | id: distribution-availability 31 | run: | 32 | go mod tidy 33 | go test distributioncheck 34 | working-directory: integration-tests/distributioncheck 35 | 36 | - name: Publish metric on X-Ray Go SDK distribution availability 37 | if: ${{ always() }} 38 | run: | 39 | if [[ "${{ steps.distribution-availability.outcome }}" == "failure" ]]; then 40 | aws cloudwatch put-metric-data --metric-name XRayGoSDKDistributionUnavailability --dimensions failure=rate --namespace MonitorSDK --value 1 --timestamp $(date +%s) 41 | else 42 | aws cloudwatch put-metric-data --metric-name XRayGoSDKDistributionUnavailability --dimensions failure=rate --namespace MonitorSDK --value 0 --timestamp $(date +%s) 43 | fi 44 | 45 | -------------------------------------------------------------------------------- /.github/workflows/ecr-publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | 6 | permissions: 7 | id-token: write 8 | contents: read 9 | 10 | jobs: 11 | build_push_sample_app_image: 12 | name: Build and Push Sample App docker image 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Create a directory 18 | run: mkdir sample-apps/http-server/aws-xray-sdk-go 19 | 20 | - name: Copy X-Ray SDK to deployment package with Sample App 21 | run: rsync -r * sample-apps/http-server/aws-xray-sdk-go --exclude sample-apps/ 22 | 23 | - name: Set up Docker Buildx 24 | uses: docker/setup-buildx-action@v1 25 | 26 | - name: Cache Docker layer 27 | uses: actions/cache@v4 28 | with: 29 | path: /tmp/.buildx-cache 30 | key: ${{ runner.os }}-buildx-${{ github.sha }} 31 | 32 | - name: Configure AWS Credentials 33 | uses: aws-actions/configure-aws-credentials@v4 34 | with: 35 | role-to-assume: ${{ secrets.AWS_INTEG_TEST_ROLE_ARN }} 36 | aws-region: us-east-1 37 | 38 | - name: Login to Public ECR 39 | uses: docker/login-action@v1 40 | with: 41 | registry: public.ecr.aws 42 | 43 | - name: Build image and push to ECR 44 | uses: docker/build-push-action@v2 45 | with: 46 | file: sample-apps/http-server/Dockerfile 47 | context: . 48 | tags: public.ecr.aws/s6b4x8l3/aws-xray-go-sdk-sample-app:${{ github.sha }} 49 | push: true 50 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release X-Ray Go SDK 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | description: The version to tag the release with, e.g., 1.2.0, 1.3.0 7 | required: true 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout master branch 14 | uses: actions/checkout@v2 15 | 16 | - name: Create Release 17 | id: create_release 18 | uses: actions/create-release@v1 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | with: 22 | tag_name: 'v${{ github.event.inputs.version }}' 23 | release_name: 'v${{ github.event.inputs.version }}' 24 | body: 'Please refer [change-log](https://github.com/aws/aws-xray-sdk-go/blob/master/CHANGELOG.md) for more details' 25 | draft: true 26 | prerelease: false 27 | -------------------------------------------------------------------------------- /.github/workflows/reviewdog.yml: -------------------------------------------------------------------------------- 1 | name: reviewdog 2 | on: [pull_request] 3 | 4 | jobs: 5 | golangci-lint: 6 | name: golangci-lint 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Check out code into the Go module directory 10 | uses: actions/checkout@v2 11 | - name: golangci-lint 12 | uses: reviewdog/action-golangci-lint@v1 13 | with: 14 | github_token: ${{ github.token }} 15 | level: warning 16 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | test: 12 | name: Test 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: 18 | - ubuntu-latest 19 | - windows-latest 20 | - macos-latest 21 | go: 22 | - '1.23' 23 | - '1.24' 24 | - '1' 25 | steps: 26 | - name: Set up Go ${{ matrix.go }} 27 | uses: actions/setup-go@v1 28 | with: 29 | go-version: ${{ matrix.go }} 30 | - name: Check out code 31 | uses: actions/checkout@v2 32 | with: 33 | path: src/github.com/aws/aws-xray-sdk-go 34 | - name: Test 35 | run: make test-with-race 36 | shell: bash 37 | working-directory: src/github.com/aws/aws-xray-sdk-go 38 | 39 | benchmark: 40 | name: XRay-Go-SDK-Benchmarking 41 | runs-on: ${{ matrix.os }} 42 | strategy: 43 | fail-fast: false 44 | matrix: 45 | os: 46 | - ubuntu-latest 47 | go: 48 | - '1.24' 49 | steps: 50 | - name: Set up Go ${{ matrix.go }} 51 | uses: actions/setup-go@v1 52 | with: 53 | go-version: ${{ matrix.go }} 54 | id: go 55 | - name: Check out code 56 | uses: actions/checkout@v2 57 | with: 58 | path: src/github.com/aws/aws-xray-sdk-go 59 | - name: Benchmark 60 | run: go test -v -benchmem -run=^$$ -bench=. ./... | tee benchmark/output.txt 61 | shell: bash 62 | working-directory: src/github.com/aws/aws-xray-sdk-go 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | run: 2 | deadline: 3m 3 | 4 | linters: 5 | disable-all: true 6 | enable: 7 | - deadcode 8 | - depguard 9 | - dupl 10 | - errcheck 11 | - gochecknoinits 12 | - gocritic 13 | - gocyclo 14 | - gofmt 15 | - goimports 16 | - golint 17 | - gosec 18 | - gosimple 19 | - govet 20 | - ineffassign 21 | - interfacer 22 | - maligned 23 | - megacheck 24 | - misspell 25 | - nakedret 26 | - prealloc 27 | - scopelint 28 | - staticcheck 29 | - structcheck 30 | - stylecheck 31 | - typecheck 32 | - unconvert 33 | - unparam 34 | - unused 35 | - varcheck 36 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws/aws-xray-sdk-go/issues), or [recently closed](https://github.com/aws/aws-xray-sdk-go/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws/aws-xray-sdk-go/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws/aws-xray-sdk-go/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | The following people have contributed to the AWS X-Ray SDK for Go's design and/or implementation (alphabetical): 3 | * Anssi Alaranta 4 | * Bhautik Pipaliya 5 | * Bilal Khan 6 | * Christopher Radek 7 | * Jacob Rickerd 8 | * James Bowman 9 | * Lulu Zhao 10 | * Muir Manders 11 | * Raniere Medeiros 12 | * Raymond Lin 13 | * Rohit Banga 14 | * Yogiraj Awati 15 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | AWS X-Ray SDK for Go 2 | Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /awsplugins/beanstalk/beanstalk.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package beanstalk 10 | 11 | import ( 12 | "encoding/json" 13 | "io/ioutil" 14 | 15 | "github.com/aws/aws-xray-sdk-go/v2/internal/logger" 16 | "github.com/aws/aws-xray-sdk-go/v2/internal/plugins" 17 | ) 18 | 19 | // Origin is the type of AWS resource that runs your application. 20 | const Origin = "AWS::ElasticBeanstalk::Environment" 21 | 22 | // Init activates ElasticBeanstalkPlugin at runtime. 23 | func Init() { 24 | if plugins.InstancePluginMetadata != nil && plugins.InstancePluginMetadata.BeanstalkMetadata == nil { 25 | addPluginMetadata(plugins.InstancePluginMetadata) 26 | } 27 | } 28 | 29 | func addPluginMetadata(pluginmd *plugins.PluginMetadata) { 30 | ebConfigPath := "/var/elasticbeanstalk/xray/environment.conf" 31 | 32 | rawConfig, err := ioutil.ReadFile(ebConfigPath) 33 | if err != nil { 34 | logger.Errorf("Unable to read Elastic Beanstalk configuration file %s: %v", ebConfigPath, err) 35 | return 36 | } 37 | 38 | config := &plugins.BeanstalkMetadata{} 39 | err = json.Unmarshal(rawConfig, config) 40 | if err != nil { 41 | logger.Errorf("Unable to unmarshal Elastic Beanstalk configuration file %s: %v", ebConfigPath, err) 42 | return 43 | } 44 | 45 | pluginmd.BeanstalkMetadata = config 46 | pluginmd.Origin = Origin 47 | } 48 | -------------------------------------------------------------------------------- /awsplugins/ec2/ec2.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package ec2 10 | 11 | import ( 12 | "bytes" 13 | "encoding/json" 14 | "net/http" 15 | 16 | "github.com/aws/aws-xray-sdk-go/v2/internal/logger" 17 | "github.com/aws/aws-xray-sdk-go/v2/internal/plugins" 18 | ) 19 | 20 | // Origin is the type of AWS resource that runs your application. 21 | const Origin = "AWS::EC2::Instance" 22 | 23 | type metadata struct { 24 | AvailabilityZone string 25 | ImageID string 26 | InstanceID string 27 | InstanceType string 28 | } 29 | 30 | // Init activates EC2Plugin at runtime. 31 | func Init() { 32 | if plugins.InstancePluginMetadata != nil && plugins.InstancePluginMetadata.EC2Metadata == nil { 33 | addPluginMetadata(plugins.InstancePluginMetadata) 34 | } 35 | } 36 | 37 | func addPluginMetadata(pluginmd *plugins.PluginMetadata) { 38 | var instanceData metadata 39 | imdsURL := "http://169.254.169.254/latest/" 40 | 41 | client := &http.Client{ 42 | Transport: http.DefaultTransport, 43 | } 44 | 45 | token, err := getToken(imdsURL, client) 46 | if err != nil { 47 | logger.Debugf("Unable to fetch EC2 instance metadata token fallback to IMDS V1: %v", err) 48 | } 49 | 50 | resp, err := getMetadata(imdsURL, client, token) 51 | if err != nil { 52 | logger.Errorf("Unable to read EC2 instance metadata: %v", err) 53 | return 54 | } 55 | 56 | buf := new(bytes.Buffer) 57 | if _, err := buf.ReadFrom(resp.Body); err != nil { 58 | logger.Errorf("Error while reading data from response buffer: %v", err) 59 | return 60 | } 61 | metadata := buf.String() 62 | 63 | if err := json.Unmarshal([]byte(metadata), &instanceData); err != nil { 64 | logger.Errorf("Error while unmarshal operation: %v", err) 65 | return 66 | } 67 | 68 | pluginmd.EC2Metadata = &plugins.EC2Metadata{InstanceID: instanceData.InstanceID, AvailabilityZone: instanceData.AvailabilityZone} 69 | pluginmd.Origin = Origin 70 | } 71 | 72 | // getToken fetches token to fetch EC2 metadata 73 | func getToken(imdsURL string, client *http.Client) (string, error) { 74 | ttlHeader := "X-aws-ec2-metadata-token-ttl-seconds" 75 | defaultTTL := "60" 76 | tokenURL := imdsURL + "api/token" 77 | 78 | req, _ := http.NewRequest("PUT", tokenURL, nil) 79 | req.Header.Add(ttlHeader, defaultTTL) 80 | resp, err := client.Do(req) 81 | if err != nil { 82 | return "", err 83 | } 84 | 85 | buf := new(bytes.Buffer) 86 | if _, err := buf.ReadFrom(resp.Body); err != nil { 87 | logger.Errorf("Error while reading data from response buffer: %v", err) 88 | return "", err 89 | } 90 | token := buf.String() 91 | 92 | return token, err 93 | } 94 | 95 | // getMetadata fetches instance metadata 96 | func getMetadata(imdsURL string, client *http.Client, token string) (*http.Response, error) { 97 | var metadataHeader string 98 | metadataURL := imdsURL + "dynamic/instance-identity/document" 99 | 100 | req, _ := http.NewRequest("GET", metadataURL, nil) 101 | if token != "" { 102 | metadataHeader = "X-aws-ec2-metadata-token" 103 | req.Header.Add(metadataHeader, token) 104 | } 105 | 106 | return client.Do(req) 107 | } 108 | 109 | // Metdata represents IMDS response. 110 | // 111 | // Deprecated: Metdata exists only for backward compatibility. 112 | type Metdata struct { 113 | AvailabilityZone string 114 | ImageID string 115 | InstanceID string 116 | InstanceType string 117 | } 118 | -------------------------------------------------------------------------------- /awsplugins/ec2/ec2_test.go: -------------------------------------------------------------------------------- 1 | package ec2 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | const testMetadata = `{ 13 | "accountId" : "123367104812", 14 | "architecture" : "x86_64", 15 | "availabilityZone" : "us-west-2a", 16 | "billingProducts" : null, 17 | "devpayProductCodes" : null, 18 | "marketplaceProductCodes" : null, 19 | "imageId" : "ami-0fe02940a29f8239b", 20 | "instanceId" : "i-032fe2d42797fb9a1", 21 | "instanceType" : "c5.xlarge", 22 | "kernelId" : null, 23 | "pendingTime" : "2020-04-21T21:16:47Z", 24 | "privateIp" : "172.19.57.109", 25 | "ramdiskId" : null, 26 | "region" : "us-west-2", 27 | "version" : "2017-09-30" 28 | }` 29 | 30 | const ( 31 | documentPath = "/dynamic/instance-identity/document" 32 | tokenPath = "/api/token" 33 | ec2Endpoint = "http://169.254.169.254/latest" 34 | ) 35 | 36 | func TestEndpoint(t *testing.T) { 37 | req, _ := http.NewRequest("GET", ec2Endpoint, nil) 38 | if e, a := ec2Endpoint, req.URL.String(); e != a { 39 | t.Errorf("expect %v, got %v", e, a) 40 | } 41 | } 42 | 43 | func TestIMDSv2Success(t *testing.T) { 44 | // Start a local HTTP server 45 | serverToken := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 46 | 47 | assert.Equal(t, req.URL.String(), tokenPath) 48 | _, _ = rw.Write([]byte("success")) 49 | })) 50 | 51 | serverMetadata := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 52 | 53 | assert.Equal(t, req.URL.String(), documentPath) 54 | assert.Equal(t, req.Header.Get("X-aws-ec2-metadata-token"), "success") 55 | _, _ = rw.Write([]byte(testMetadata)) 56 | })) 57 | 58 | defer serverToken.Close() 59 | defer serverMetadata.Close() 60 | 61 | client := &http.Client{ 62 | Transport: http.DefaultTransport, 63 | } 64 | 65 | // token fetch success 66 | respToken, _ := getToken(serverToken.URL+"/", client) 67 | assert.NotEqual(t, respToken, "") 68 | 69 | // successfully metadata fetch using IMDS v2 70 | respMetadata, _ := getMetadata(serverMetadata.URL+"/", client, respToken) 71 | ec2Metadata, _ := ioutil.ReadAll(respMetadata.Body) 72 | assert.Equal(t, []byte(testMetadata), ec2Metadata) 73 | } 74 | 75 | func TestIMDSv2Failv1Success(t *testing.T) { 76 | // Start a local HTTP server 77 | serverToken := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 78 | 79 | assert.Equal(t, req.URL.String(), tokenPath) 80 | _, _ = rw.Write([]byte("success")) 81 | })) 82 | 83 | serverMetadata := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 84 | 85 | assert.Equal(t, req.URL.String(), documentPath) 86 | _, _ = rw.Write([]byte(testMetadata)) 87 | })) 88 | 89 | defer serverToken.Close() 90 | defer serverMetadata.Close() 91 | 92 | client := &http.Client{ 93 | Transport: http.DefaultTransport, 94 | } 95 | 96 | // token fetch fail 97 | respToken, _ := getToken("/", client) 98 | assert.Equal(t, respToken, "") 99 | 100 | // fallback to IMDSv1 and successfully metadata fetch using IMDSv1 101 | respMetadata, _ := getMetadata(serverMetadata.URL+"/", client, respToken) 102 | ec2Metadata, _ := ioutil.ReadAll(respMetadata.Body) 103 | assert.Equal(t, []byte(testMetadata), ec2Metadata) 104 | } 105 | 106 | func TestIMDSv2Failv1Fail(t *testing.T) { 107 | // Start a local HTTP server 108 | serverToken := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 109 | assert.Equal(t, req.URL.String(), tokenPath) 110 | _, _ = rw.Write([]byte("success")) 111 | })) 112 | 113 | serverMetadata := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 114 | 115 | assert.Equal(t, req.URL.String(), documentPath) 116 | _, _ = rw.Write([]byte(testMetadata)) 117 | })) 118 | 119 | defer serverToken.Close() 120 | defer serverMetadata.Close() 121 | 122 | client := &http.Client{ 123 | Transport: http.DefaultTransport, 124 | } 125 | 126 | // token fetch fail 127 | respToken, _ := getToken("/", client) 128 | assert.Equal(t, respToken, "") 129 | 130 | // fallback to IMDSv1 and fail metadata fetch using IMDSv1 131 | _, err := getMetadata("/", client, respToken) 132 | assert.Error(t, err) 133 | } 134 | -------------------------------------------------------------------------------- /awsplugins/ecs/ecs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package ecs 10 | 11 | import ( 12 | "os" 13 | 14 | "github.com/aws/aws-xray-sdk-go/v2/internal/logger" 15 | "github.com/aws/aws-xray-sdk-go/v2/internal/plugins" 16 | ) 17 | 18 | // Origin is the type of AWS resource that runs your application. 19 | const Origin = "AWS::ECS::Container" 20 | 21 | // Init activates ECSPlugin at runtime. 22 | func Init() { 23 | if plugins.InstancePluginMetadata != nil && plugins.InstancePluginMetadata.ECSMetadata == nil { 24 | addPluginMetadata(plugins.InstancePluginMetadata) 25 | } 26 | } 27 | 28 | func addPluginMetadata(pluginmd *plugins.PluginMetadata) { 29 | hostname, err := os.Hostname() 30 | 31 | if err != nil { 32 | logger.Errorf("Unable to retrieve hostname from OS. %v", err) 33 | return 34 | } 35 | 36 | pluginmd.ECSMetadata = &plugins.ECSMetadata{ContainerName: hostname} 37 | pluginmd.Origin = Origin 38 | } 39 | -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | # Benchmark Instructions for AWS X-Ray Go SDK 2 | AWS X-Ray Go SDK introduced benchmarks to identify performance bottlenecks of AWS X-Ray Go SDK codebase. Moreover, benchmarks can be used to identify data races and locking issues. Below are the instructions on how to run AWS X-Ray Go SDK benchmarks using Go commands and makefile. 3 | 4 | An example of the benchmark output can be found [here](./results/1.0.0.md). 5 | 6 | ## Run all the benchmarks using Go Command 7 | ``` 8 | go test -benchmem -run=^$$ -bench=. ./... 9 | ``` 10 | 11 | ## Run all the benchmark using makefile 12 | Running below command will generate benchmark_sdk.md for analysis. To avoid excessive logging change the loglevel to LogLevelError. 13 | ``` 14 | make benchmark_sdk 15 | ``` 16 | ## Run memory profiling of xray package using makefile 17 | Running below command will generate benchmark_xray_mem.md for analysis. 18 | ``` 19 | make benchmark_xray_mem 20 | ``` 21 | ## Run cpu profiling of xray package using makefile 22 | Running below command will generate benchmark_xray_cpu.md for analysis. 23 | ``` 24 | make benchmark_xray_cpu 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /benchmark/results/1.0.0.md: -------------------------------------------------------------------------------- 1 | # Example Benchmark Results 2 | 3 | The below table contains a formatted output of the benchmarking results for v1.0.0 of the SDK. It was run on a m5.xlarge EC2 instance, with 16 GB of memory and 4 vCPUs. 4 | 5 | | Operation | Iterations | Latency | Memory | Allocs | 6 | | --------------------------------------------------- | ------------- | ------------- | ------------- | ----------- | 7 | |BenchmarkGetDaemonEndpoints-4 |3846729 |293 ns/op |144 B/op |5 allocs/op | 8 | |BenchmarkGetDaemonEndpointsFromEnv_DoubleParse-4 |784056 |1544 ns/op |432 B/op |14 allocs/op| 9 | |BenchmarkGetDaemonEndpointsFromEnv_SingleParse-4 |1000000000 |0.000009 ns/op |0 B/op |0 allocs/op 10 | |BenchmarkGetDaemonEndpointsFromString-4 |1000000000 |0.000015 ns/op |0 B/op |0 allocs/op 11 | |BenchmarkFromString-4 |2442208 |492 ns/op |480 B/op |4 allocs/op 12 | |BenchmarkWildcardMatch-4 |47175990 |25.1 ns/op |0 B/op |0 allocs/op 13 | |BenchmarkDefaultFormattingStrategy_Error-4 |676300 |1574 ns/op |496 B/op |4 allocs/op 14 | |BenchmarkDefaultFormattingStrategy_ExceptionFromError-4 |638359 |1875 ns/op |408 B/op |10 allocs/op 15 | |BenchmarkDefaultFormattingStrategy_Panic-4 |269598 |4488 ns/op |1344 B/op |8 allocs/op 16 | |BenchmarkCentralizedManifest_putRule-4 |2163954 |556 ns/op |416 B/op |5 allocs/op 17 | |BenchmarkCentralizedStrategy_ShouldTrace-4 |1520000 |789 ns/op |384 B/op |11 allocs/op 18 | |BenchmarkNewCentralizedStrategy_refreshManifest-4 |2000916 |599 ns/op |112 B/op |2 allocs/op 19 | |BenchmarkCentralizedStrategy_refreshTargets-4 |8582516 |141 ns/op |56 B/op |2 allocs/op 20 | |BenchmarkCentralizedRule_Sample-4 |6970968 |172 ns/op |288 B/op |5 allocs/op 21 | |BenchmarkNewLocalizedStrategyFromJSONBytes-4 |1000000000 |0.000005 ns/op |0 B/op |0 allocs/op 22 | |BenchmarkCapture-4 |465134 |2533 ns/op |681 B/op |12 allocs/op 23 | |BenchmarkClient-4 |1000000000 |0.647 ns/op |0 B/op |0 allocs/op 24 | |BenchmarkConfigure-4 |5442780 |219 ns/op |0 B/op |0 allocs/op 25 | |BenchmarkGetRecorder-4 |100000000 |10.8 ns/op |0 B/op |0 allocs/op 26 | |BenchmarkGetSegment-4 |186037935 |6.45 ns/op |0 B/op |0 allocs/op 27 | |BenchmarkDetachContext-4 |18997809 |62.8 ns/op |48 B/op |1 allocs/op 28 | |BenchmarkAddAnnotation-4 |13307188 |82.8 ns/op |0 B/op |0 allocs/op 29 | |BenchmarkAddMetadata-4 |12797424 |93.2 ns/op |0 B/op |0 allocs/op 30 | |BenchmarkDefaultEmitter_packSegments-4 |1531 |11728896 ns/op |2721945 B/op |50582 allocs/op 31 | |BenchmarkDefaultEmitter-4 |178814 |6600 ns/op |584 B/op |17 allocs/op 32 | |BenchmarkHandler-4 |4251 |274168 ns/op |31603 B/op |247 allocs/op 33 | |BenchmarkBeginSegment-4 |64215 |20233 ns/op |3264 B/op |40 allocs/op 34 | |BenchmarkBeginSubsegment-4 |537710 |2412 ns/op |674 B/op |12 allocs/op 35 | |BenchmarkAddError-4 |481357 |2784 ns/op |931 B/op |5 allocs/op 36 | -------------------------------------------------------------------------------- /daemoncfg/daemon_config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package daemoncfg 10 | 11 | import ( 12 | "net" 13 | "os" 14 | "strconv" 15 | "strings" 16 | 17 | "github.com/aws/aws-xray-sdk-go/v2/internal/logger" 18 | "github.com/pkg/errors" 19 | ) 20 | 21 | var addressDelimiter = " " // delimiter between tcp and udp addresses 22 | var udpKey = "udp" 23 | var tcpKey = "tcp" 24 | 25 | // DaemonEndpoints stores X-Ray daemon configuration about the ip address and port for UDP and TCP port. It gets the address 26 | // string from "AWS_TRACING_DAEMON_ADDRESS" and then from recorder's configuration for DaemonAddr. 27 | // A notation of '127.0.0.1:2000' or 'tcp:127.0.0.1:2000 udp:127.0.0.2:2001' or 'udp:127.0.0.1:2000 tcp:127.0.0.2:2001' 28 | // are both acceptable. The first one means UDP and TCP are running at the same address. 29 | // Notation 'hostname:2000' or 'tcp:hostname:2000 udp:hostname:2001' or 'udp:hostname:2000 tcp:hostname:2001' are also acceptable. 30 | // By default it assumes a X-Ray daemon running at 127.0.0.1:2000 listening to both UDP and TCP traffic. 31 | type DaemonEndpoints struct { 32 | // UDPAddr represents UDP endpoint for segments to be sent by emitter. 33 | UDPAddr *net.UDPAddr 34 | // TCPAddr represents TCP endpoint of the daemon to make sampling API calls. 35 | TCPAddr *net.TCPAddr 36 | } 37 | 38 | // GetDaemonEndpoints returns DaemonEndpoints. 39 | func GetDaemonEndpoints() *DaemonEndpoints { 40 | daemonEndpoint, err := GetDaemonEndpointsFromString("") // only environment variable would be parsed 41 | 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | if daemonEndpoint == nil { // env variable not set 47 | return GetDefaultDaemonEndpoints() 48 | } 49 | return daemonEndpoint // env variable successfully parsed 50 | } 51 | 52 | // GetDaemonEndpointsFromEnv resolves the daemon address if set in the environment variable. 53 | func GetDaemonEndpointsFromEnv() (*DaemonEndpoints, error) { 54 | if envDaemonAddr := os.Getenv("AWS_XRAY_DAEMON_ADDRESS"); envDaemonAddr != "" { 55 | return resolveAddress(envDaemonAddr) 56 | } 57 | return nil, nil 58 | } 59 | 60 | // GetDefaultDaemonEndpoints returns the default UDP and TCP address of the daemon. 61 | func GetDefaultDaemonEndpoints() *DaemonEndpoints { 62 | udpAddr := &net.UDPAddr{ 63 | IP: net.IPv4(127, 0, 0, 1), 64 | Port: 2000, 65 | } 66 | 67 | tcpAddr := &net.TCPAddr{ 68 | IP: net.IPv4(127, 0, 0, 1), 69 | Port: 2000, 70 | } 71 | 72 | return &DaemonEndpoints{ 73 | UDPAddr: udpAddr, 74 | TCPAddr: tcpAddr, 75 | } 76 | } 77 | 78 | // GetDaemonEndpointsFromString parses provided daemon address if the environment variable is invalid or not set. 79 | // DaemonEndpoints is non nil if the env variable or provided address is valid. 80 | func GetDaemonEndpointsFromString(dAddr string) (*DaemonEndpoints, error) { 81 | var daemonAddr string 82 | // Try to get the X-Ray daemon address from an environment variable 83 | if envDaemonAddr := os.Getenv("AWS_XRAY_DAEMON_ADDRESS"); envDaemonAddr != "" { 84 | daemonAddr = envDaemonAddr 85 | logger.Infof("using daemon endpoints from environment variable AWS_XRAY_DAEMON_ADDRESS: %v", envDaemonAddr) 86 | } else if dAddr != "" { 87 | daemonAddr = dAddr 88 | } 89 | if daemonAddr != "" { 90 | return resolveAddress(daemonAddr) 91 | } 92 | return nil, nil 93 | } 94 | 95 | func resolveAddress(dAddr string) (*DaemonEndpoints, error) { 96 | addr := strings.Split(dAddr, addressDelimiter) 97 | switch len(addr) { 98 | case 1: 99 | return parseSingleForm(addr[0]) 100 | case 2: 101 | return parseDoubleForm(addr) 102 | } 103 | return nil, errors.New("invalid daemon address: " + dAddr) 104 | } 105 | 106 | func parseDoubleForm(addr []string) (*DaemonEndpoints, error) { 107 | addr1 := strings.Split(addr[0], ":") // tcp:127.0.0.1:2000 or udp:127.0.0.1:2000 108 | addr2 := strings.Split(addr[1], ":") // tcp:127.0.0.1:2000 or udp:127.0.0.1:2000 109 | 110 | if len(addr1) != 3 || len(addr2) != 3 { 111 | return nil, errors.New("invalid daemon address: " + addr[0] + " " + addr[1]) 112 | } 113 | 114 | // validate ports 115 | _, pErr1 := strconv.Atoi(addr1[2]) 116 | _, pErr2 := strconv.Atoi(addr1[2]) 117 | 118 | if pErr1 != nil || pErr2 != nil { 119 | return nil, errors.New("invalid daemon address port") 120 | } 121 | 122 | addrMap := make(map[string]string) 123 | 124 | addrMap[addr1[0]] = addr1[1] + ":" + addr1[2] 125 | addrMap[addr2[0]] = addr2[1] + ":" + addr2[2] 126 | 127 | if addrMap[udpKey] == "" || addrMap[tcpKey] == "" { // for double form, tcp and udp keywords should be present 128 | return nil, errors.New("invalid daemon address") 129 | } 130 | 131 | udpAddr, uErr := resolveUDPAddr(addrMap[udpKey]) 132 | if uErr != nil { 133 | return nil, uErr 134 | } 135 | 136 | tcpAddr, tErr := resolveTCPAddr(addrMap[tcpKey]) 137 | if tErr != nil { 138 | return nil, tErr 139 | } 140 | 141 | return &DaemonEndpoints{ 142 | UDPAddr: udpAddr, 143 | TCPAddr: tcpAddr, 144 | }, nil 145 | } 146 | 147 | func parseSingleForm(addr string) (*DaemonEndpoints, error) { // format = "ip:port" 148 | a := strings.Split(addr, ":") // 127.0.0.1:2000 149 | 150 | if len(a) != 2 { 151 | return nil, errors.New("invalid daemon address: " + addr) 152 | } 153 | 154 | // validate port 155 | _, pErr1 := strconv.Atoi(a[1]) 156 | 157 | if pErr1 != nil { 158 | return nil, errors.New("invalid daemon address port") 159 | } 160 | 161 | udpAddr, uErr := resolveUDPAddr(addr) 162 | if uErr != nil { 163 | return nil, uErr 164 | } 165 | tcpAddr, tErr := resolveTCPAddr(addr) 166 | if tErr != nil { 167 | return nil, tErr 168 | } 169 | 170 | return &DaemonEndpoints{ 171 | UDPAddr: udpAddr, 172 | TCPAddr: tcpAddr, 173 | }, nil 174 | } 175 | 176 | func resolveUDPAddr(s string) (*net.UDPAddr, error) { 177 | return net.ResolveUDPAddr(udpKey, s) 178 | } 179 | 180 | func resolveTCPAddr(s string) (*net.TCPAddr, error) { 181 | return net.ResolveTCPAddr(tcpKey, s) 182 | } 183 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aws/aws-xray-sdk-go/v2 2 | 3 | go 1.24 4 | 5 | require ( 6 | github.com/DATA-DOG/go-sqlmock v1.5.1 7 | github.com/aws/aws-lambda-go v1.41.0 8 | github.com/aws/aws-sdk-go-v2 v1.22.2 9 | github.com/aws/aws-sdk-go-v2/service/route53 v1.6.2 10 | github.com/aws/smithy-go v1.16.0 11 | github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 12 | github.com/pkg/errors v0.9.1 13 | github.com/stretchr/testify v1.8.4 14 | github.com/valyala/fasthttp v1.52.0 15 | golang.org/x/net v0.38.0 16 | google.golang.org/grpc v1.64.1 17 | google.golang.org/protobuf v1.33.0 18 | ) 19 | 20 | require ( 21 | github.com/andybalholm/brotli v1.1.0 // indirect 22 | github.com/davecgh/go-spew v1.1.1 // indirect 23 | github.com/jmespath/go-jmespath v0.4.0 // indirect 24 | github.com/klauspost/compress v1.17.6 // indirect 25 | github.com/pmezard/go-difflib v1.0.0 // indirect 26 | github.com/valyala/bytebufferpool v1.0.0 // indirect 27 | golang.org/x/sys v0.31.0 // indirect 28 | golang.org/x/text v0.23.0 // indirect 29 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect 30 | gopkg.in/yaml.v3 v3.0.1 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/DATA-DOG/go-sqlmock v1.5.1 h1:FK6RCIUSfmbnI/imIICmboyQBkOckutaa6R5YYlLZyo= 2 | github.com/DATA-DOG/go-sqlmock v1.5.1/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= 3 | github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= 4 | github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= 5 | github.com/aws/aws-lambda-go v1.41.0 h1:l/5fyVb6Ud9uYd411xdHZzSf2n86TakxzpvIoz7l+3Y= 6 | github.com/aws/aws-lambda-go v1.41.0/go.mod h1:jwFe2KmMsHmffA1X2R09hH6lFzJQxzI8qK17ewzbQMM= 7 | github.com/aws/aws-sdk-go-v2 v1.6.0/go.mod h1:tI4KhsR5VkzlUa2DZAdwx7wCAYGwkZZ1H31PYrBFx1w= 8 | github.com/aws/aws-sdk-go-v2 v1.22.2 h1:lV0U8fnhAnPz8YcdmZVV60+tr6CakHzqA6P8T46ExJI= 9 | github.com/aws/aws-sdk-go-v2 v1.22.2/go.mod h1:Kd0OJtkW3Q0M0lUWGszapWjEvrXDzRW+D21JNsroB+c= 10 | github.com/aws/aws-sdk-go-v2/service/route53 v1.6.2 h1:OsggywXCk9iFKdu2Aopg3e1oJITIuyW36hA/B0rqupE= 11 | github.com/aws/aws-sdk-go-v2/service/route53 v1.6.2/go.mod h1:ZnAMilx42P7DgIrdjlWCkNIGSBLzeyk6T31uB8oGTwY= 12 | github.com/aws/smithy-go v1.4.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= 13 | github.com/aws/smithy-go v1.16.0 h1:gJZEH/Fqh+RsvlJ1Zt4tVAtV6bKkp3cC+R6FCZMNzik= 14 | github.com/aws/smithy-go v1.16.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= 15 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 17 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 19 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 20 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 21 | github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= 22 | github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= 23 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 24 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 25 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 26 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 27 | github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= 28 | github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= 29 | github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 30 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 31 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 32 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 33 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 34 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 35 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 36 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 37 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 38 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 39 | github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0= 40 | github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= 41 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 42 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 43 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 44 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 45 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 46 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 47 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 48 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= 49 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= 50 | google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= 51 | google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= 52 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 53 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 54 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 55 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 56 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 57 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 58 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 59 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 60 | -------------------------------------------------------------------------------- /header/header.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package header 10 | 11 | import ( 12 | "bytes" 13 | "strings" 14 | ) 15 | 16 | const ( 17 | // RootPrefix is the prefix for 18 | // Root attribute in X-Amzn-Trace-Id. 19 | RootPrefix = "Root=" 20 | 21 | // ParentPrefix is the prefix for 22 | // Parent attribute in X-Amzn-Trace-Id. 23 | ParentPrefix = "Parent=" 24 | 25 | // SampledPrefix is the prefix for 26 | // Sampled attribute in X-Amzn-Trace-Id. 27 | SampledPrefix = "Sampled=" 28 | 29 | // SelfPrefix is the prefix for 30 | // Self attribute in X-Amzn-Trace-Id. 31 | SelfPrefix = "Self=" 32 | ) 33 | 34 | // SamplingDecision is a string representation of 35 | // whether or not the current segment has been sampled. 36 | type SamplingDecision string 37 | 38 | const ( 39 | // Sampled indicates the current segment has been 40 | // sampled and will be sent to the X-Ray daemon. 41 | Sampled SamplingDecision = "Sampled=1" 42 | 43 | // NotSampled indicates the current segment has 44 | // not been sampled. 45 | NotSampled SamplingDecision = "Sampled=0" 46 | 47 | // Requested indicates sampling decision will be 48 | // made by the downstream service and propagated 49 | // back upstream in the response. 50 | Requested SamplingDecision = "Sampled=?" 51 | 52 | // Unknown indicates no sampling decision will be made. 53 | Unknown SamplingDecision = "" 54 | ) 55 | 56 | func samplingDecision(s string) SamplingDecision { 57 | switch s { 58 | case string(Sampled): 59 | return Sampled 60 | case string(NotSampled): 61 | return NotSampled 62 | case string(Requested): 63 | return Requested 64 | } 65 | return Unknown 66 | } 67 | 68 | // Header is the value of X-Amzn-Trace-Id. 69 | type Header struct { 70 | TraceID string 71 | ParentID string 72 | SamplingDecision SamplingDecision 73 | 74 | AdditionalData map[string]string 75 | } 76 | 77 | // FromString gets individual value for each item in Header struct. 78 | func FromString(s string) *Header { 79 | ret := &Header{ 80 | SamplingDecision: Unknown, 81 | AdditionalData: make(map[string]string), 82 | } 83 | parts := strings.Split(s, ";") 84 | for i := range parts { 85 | p := strings.TrimSpace(parts[i]) 86 | value, valid := valueFromKeyValuePair(p) 87 | if valid { 88 | switch { 89 | case strings.HasPrefix(p, RootPrefix): 90 | ret.TraceID = value 91 | case strings.HasPrefix(p, ParentPrefix): 92 | ret.ParentID = value 93 | case strings.HasPrefix(p, SampledPrefix): 94 | ret.SamplingDecision = samplingDecision(p) 95 | case !strings.HasPrefix(p, SelfPrefix): 96 | key, valid := keyFromKeyValuePair(p) 97 | if valid { 98 | ret.AdditionalData[key] = value 99 | } 100 | } 101 | } 102 | } 103 | return ret 104 | } 105 | 106 | // String returns a string representation for header. 107 | func (h Header) String() string { 108 | var p [][]byte 109 | if h.TraceID != "" { 110 | p = append(p, []byte(RootPrefix+h.TraceID)) 111 | } 112 | if h.ParentID != "" { 113 | p = append(p, []byte(ParentPrefix+h.ParentID)) 114 | } 115 | p = append(p, []byte(h.SamplingDecision)) 116 | for key := range h.AdditionalData { 117 | p = append(p, []byte(key+"="+h.AdditionalData[key])) 118 | } 119 | return string(bytes.Join(p, []byte(";"))) 120 | } 121 | 122 | func keyFromKeyValuePair(s string) (string, bool) { 123 | e := strings.Index(s, "=") 124 | if -1 != e { 125 | return s[:e], true 126 | } 127 | return "", false 128 | } 129 | 130 | func valueFromKeyValuePair(s string) (string, bool) { 131 | e := strings.Index(s, "=") 132 | if -1 != e { 133 | return s[e+1:], true 134 | } 135 | return "", false 136 | } 137 | -------------------------------------------------------------------------------- /header/header_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package header 10 | 11 | import ( 12 | "testing" 13 | 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | const ExampleTraceID string = "0-57ff426a-80c11c39b0c928905eb0828d" 18 | 19 | func TestSampledEqualsOneFromString(t *testing.T) { 20 | h := FromString("Sampled=1") 21 | 22 | assert.Equal(t, Sampled, h.SamplingDecision) 23 | assert.Empty(t, h.TraceID) 24 | assert.Empty(t, h.ParentID) 25 | assert.Empty(t, h.AdditionalData) 26 | } 27 | 28 | func TestLonghFromString(t *testing.T) { 29 | h := FromString("Sampled=?;Root=" + ExampleTraceID + ";Parent=foo;Self=2;Foo=bar") 30 | 31 | assert.Equal(t, Requested, h.SamplingDecision) 32 | assert.Equal(t, ExampleTraceID, h.TraceID) 33 | assert.Equal(t, "foo", h.ParentID) 34 | assert.Equal(t, 1, len(h.AdditionalData)) 35 | assert.Equal(t, "bar", h.AdditionalData["Foo"]) 36 | } 37 | 38 | func TestLonghFromStringWithSpaces(t *testing.T) { 39 | h := FromString("Sampled=?; Root=" + ExampleTraceID + "; Parent=foo; Self=2; Foo=bar") 40 | 41 | assert.Equal(t, Requested, h.SamplingDecision) 42 | assert.Equal(t, ExampleTraceID, h.TraceID) 43 | assert.Equal(t, "foo", h.ParentID) 44 | assert.Equal(t, 1, len(h.AdditionalData)) 45 | assert.Equal(t, "bar", h.AdditionalData["Foo"]) 46 | } 47 | 48 | func TestSampledUnknownToString(t *testing.T) { 49 | h := &Header{} 50 | h.SamplingDecision = Unknown 51 | assert.Equal(t, "", h.String()) 52 | } 53 | 54 | func TestSampledEqualsOneToString(t *testing.T) { 55 | h := &Header{} 56 | h.SamplingDecision = Sampled 57 | assert.Equal(t, "Sampled=1", h.String()) 58 | } 59 | 60 | func TestSampledEqualsOneAndParentToString(t *testing.T) { 61 | h := &Header{} 62 | h.SamplingDecision = Sampled 63 | h.ParentID = "foo" 64 | assert.Equal(t, "Parent=foo;Sampled=1", h.String()) 65 | } 66 | 67 | func TestLonghToString(t *testing.T) { 68 | h := &Header{} 69 | h.SamplingDecision = Sampled 70 | h.TraceID = ExampleTraceID 71 | h.ParentID = "foo" 72 | h.AdditionalData = make(map[string]string) 73 | h.AdditionalData["Foo"] = "bar" 74 | assert.Equal(t, "Root="+ExampleTraceID+";Parent=foo;Sampled=1;Foo=bar", h.String()) 75 | } 76 | 77 | // Benchmark 78 | func BenchmarkFromString(b *testing.B) { 79 | str := "Sampled=?; Root=" + ExampleTraceID + "; Parent=foo; Self=2; Foo=bar" 80 | 81 | for i := 0; i < b.N; i++ { 82 | FromString(str) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /images/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-xray-sdk-go/24cbdf8f5e32a9fada328b2ab9b1f343a7c52383/images/example.png -------------------------------------------------------------------------------- /instrumentation/awsv2/awsv2.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package awsv2 10 | 11 | import ( 12 | "context" 13 | 14 | v2Middleware "github.com/aws/aws-sdk-go-v2/aws/middleware" 15 | "github.com/aws/smithy-go/middleware" 16 | smithyhttp "github.com/aws/smithy-go/transport/http" 17 | "github.com/aws/aws-xray-sdk-go/v2/xray" 18 | ) 19 | 20 | // RequestIDKey is the key name of the request id. 21 | const RequestIDKey string = "request_id" 22 | 23 | // ExtendedRequestIDKey is the key name of the extend request id. 24 | const ExtendedRequestIDKey string = "id_2" 25 | 26 | // S3ExtendedRequestIDHeaderKey is the key name of the s3 extend request id. 27 | const S3ExtendedRequestIDHeaderKey string = "x-amz-id-2" 28 | 29 | type awsV2SubsegmentKey struct{} 30 | 31 | func initializeMiddlewareAfter(stack *middleware.Stack) error { 32 | return stack.Initialize.Add(middleware.InitializeMiddlewareFunc("XRayInitializeMiddlewareAfter", func( 33 | ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler) ( 34 | out middleware.InitializeOutput, metadata middleware.Metadata, err error) { 35 | 36 | serviceName := v2Middleware.GetServiceID(ctx) 37 | // Start the subsegment 38 | ctx, subseg := xray.BeginSubsegment(ctx, serviceName) 39 | if subseg == nil { 40 | return next.HandleInitialize(ctx, in) 41 | } 42 | subseg.Namespace = "aws" 43 | subseg.GetAWS()["region"] = v2Middleware.GetRegion(ctx) 44 | subseg.GetAWS()["operation"] = v2Middleware.GetOperationName(ctx) 45 | 46 | // set the subsegment in the context 47 | ctx = context.WithValue(ctx, awsV2SubsegmentKey{}, subseg) 48 | 49 | out, metadata, err = next.HandleInitialize(ctx, in) 50 | 51 | // End the subsegment when the response returns from this middleware 52 | defer subseg.Close(err) 53 | 54 | return out, metadata, err 55 | }), 56 | middleware.After) 57 | } 58 | 59 | func deserializeMiddleware(stack *middleware.Stack) error { 60 | return stack.Deserialize.Add(middleware.DeserializeMiddlewareFunc("XRayDeserializeMiddleware", func( 61 | ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler) ( 62 | out middleware.DeserializeOutput, metadata middleware.Metadata, err error) { 63 | 64 | subseg, ok := ctx.Value(awsV2SubsegmentKey{}).(*xray.Segment) 65 | if !ok { 66 | return next.HandleDeserialize(ctx, in) 67 | } 68 | 69 | in.Request.(*smithyhttp.Request).Header.Set(xray.TraceIDHeaderKey, subseg.DownstreamHeader().String()) 70 | 71 | out, metadata, err = next.HandleDeserialize(ctx, in) 72 | 73 | resp, ok := out.RawResponse.(*smithyhttp.Response) 74 | if !ok { 75 | // No raw response to wrap with. 76 | return out, metadata, err 77 | } 78 | 79 | // Lock subseg before updating 80 | subseg.Lock() 81 | 82 | subseg.GetHTTP().GetResponse().ContentLength = int(resp.ContentLength) 83 | requestID, ok := v2Middleware.GetRequestIDMetadata(metadata) 84 | 85 | if ok { 86 | subseg.GetAWS()[RequestIDKey] = requestID 87 | } 88 | if extendedRequestID := resp.Header.Get(S3ExtendedRequestIDHeaderKey); extendedRequestID != "" { 89 | subseg.GetAWS()[ExtendedRequestIDKey] = extendedRequestID 90 | } 91 | 92 | subseg.Unlock() 93 | 94 | xray.HttpCaptureResponse(subseg, resp.StatusCode) 95 | return out, metadata, err 96 | }), 97 | middleware.Before) 98 | } 99 | 100 | func AWSV2Instrumentor(apiOptions *[]func(*middleware.Stack) error) { 101 | *apiOptions = append(*apiOptions, initializeMiddlewareAfter, deserializeMiddleware) 102 | } 103 | -------------------------------------------------------------------------------- /integration-tests/distributioncheck/go.mod: -------------------------------------------------------------------------------- 1 | module distributioncheck 2 | 3 | replace github.com/aws/aws-xray-sdk-go/v2 => ../../ 4 | 5 | require ( 6 | github.com/aws/aws-xray-sdk-go/v2 v2.0.0 7 | github.com/stretchr/testify v1.8.4 8 | ) 9 | 10 | require ( 11 | github.com/andybalholm/brotli v1.1.0 // indirect 12 | github.com/davecgh/go-spew v1.1.1 // indirect 13 | github.com/klauspost/compress v1.17.6 // indirect 14 | github.com/pkg/errors v0.9.1 // indirect 15 | github.com/pmezard/go-difflib v1.0.0 // indirect 16 | github.com/valyala/bytebufferpool v1.0.0 // indirect 17 | github.com/valyala/fasthttp v1.52.0 // indirect 18 | golang.org/x/net v0.38.0 // indirect 19 | golang.org/x/sys v0.31.0 // indirect 20 | golang.org/x/text v0.23.0 // indirect 21 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect 22 | google.golang.org/grpc v1.64.1 // indirect 23 | google.golang.org/protobuf v1.33.0 // indirect 24 | gopkg.in/yaml.v3 v3.0.1 // indirect 25 | ) 26 | 27 | go 1.24 28 | -------------------------------------------------------------------------------- /integration-tests/distributioncheck/go.sum: -------------------------------------------------------------------------------- 1 | github.com/DATA-DOG/go-sqlmock v1.5.1 h1:FK6RCIUSfmbnI/imIICmboyQBkOckutaa6R5YYlLZyo= 2 | github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= 3 | github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 7 | github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= 8 | github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= 9 | github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 10 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 11 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 14 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 15 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 16 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 17 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 18 | github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0= 19 | github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= 20 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 21 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 22 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 23 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 24 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 25 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 26 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= 27 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= 28 | google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= 29 | google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= 30 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 31 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 32 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 33 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 34 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 35 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 36 | -------------------------------------------------------------------------------- /integration-tests/distributioncheck/sanity_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package distributioncheck 10 | 11 | import ( 12 | "context" 13 | "testing" 14 | 15 | "github.com/aws/aws-xray-sdk-go/v2/xray" 16 | "github.com/stretchr/testify/assert" 17 | ) 18 | 19 | func TestCreateSegment(t *testing.T) { 20 | _, seg := xray.BeginSegment(context.Background(), "test") 21 | assert.Equal(t, "test", seg.Name) 22 | } 23 | -------------------------------------------------------------------------------- /internal/logger/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package logger 10 | 11 | import ( 12 | "fmt" 13 | "os" 14 | 15 | "github.com/aws/aws-xray-sdk-go/v2/xraylog" 16 | ) 17 | 18 | // This internal package hides the actual logging functions from the user. 19 | 20 | // Logger instance used by xray to log. Set via xray.SetLogger(). 21 | var Logger xraylog.Logger = xraylog.NewDefaultLogger(os.Stdout, xraylog.LogLevelInfo) 22 | 23 | func Debugf(format string, args ...interface{}) { 24 | Logger.Log(xraylog.LogLevelDebug, printfArgs{format, args}) 25 | } 26 | 27 | func Debug(args ...interface{}) { 28 | Logger.Log(xraylog.LogLevelDebug, printArgs(args)) 29 | } 30 | 31 | func DebugDeferred(fn func() string) { 32 | Logger.Log(xraylog.LogLevelDebug, stringerFunc(fn)) 33 | } 34 | 35 | func Infof(format string, args ...interface{}) { 36 | Logger.Log(xraylog.LogLevelInfo, printfArgs{format, args}) 37 | } 38 | 39 | func Info(args ...interface{}) { 40 | Logger.Log(xraylog.LogLevelInfo, printArgs(args)) 41 | } 42 | 43 | func Warnf(format string, args ...interface{}) { 44 | Logger.Log(xraylog.LogLevelWarn, printfArgs{format, args}) 45 | } 46 | 47 | func Warn(args ...interface{}) { 48 | Logger.Log(xraylog.LogLevelWarn, printArgs(args)) 49 | } 50 | 51 | func Errorf(format string, args ...interface{}) { 52 | Logger.Log(xraylog.LogLevelError, printfArgs{format, args}) 53 | } 54 | 55 | func Error(args ...interface{}) { 56 | Logger.Log(xraylog.LogLevelError, printArgs(args)) 57 | } 58 | 59 | type printfArgs struct { 60 | format string 61 | args []interface{} 62 | } 63 | 64 | func (p printfArgs) String() string { 65 | return fmt.Sprintf(p.format, p.args...) 66 | } 67 | 68 | type printArgs []interface{} 69 | 70 | func (p printArgs) String() string { 71 | return fmt.Sprint([]interface{}(p)...) 72 | } 73 | 74 | type stringerFunc func() string 75 | 76 | func (sf stringerFunc) String() string { 77 | return sf() 78 | } 79 | -------------------------------------------------------------------------------- /internal/logger/logger_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package logger 10 | 11 | import ( 12 | "bytes" 13 | "strings" 14 | "testing" 15 | 16 | "github.com/aws/aws-xray-sdk-go/v2/xraylog" 17 | ) 18 | 19 | func TestLogger(t *testing.T) { 20 | oldLogger := Logger 21 | defer func() { Logger = oldLogger }() 22 | 23 | var buf bytes.Buffer 24 | 25 | // filter properly by level 26 | Logger = xraylog.NewDefaultLogger(&buf, xraylog.LogLevelWarn) 27 | 28 | Debug("debug") 29 | Info("info") 30 | Warn("warn") 31 | Error("error") 32 | 33 | gotLines := strings.Split(strings.TrimSpace(buf.String()), "\n") 34 | if len(gotLines) != 2 { 35 | t.Fatalf("got %d lines", len(gotLines)) 36 | } 37 | 38 | if !strings.Contains(gotLines[0], "[WARN] warn") { 39 | t.Error("expected first line to be warn") 40 | } 41 | 42 | if !strings.Contains(gotLines[1], "[ERROR] error") { 43 | t.Error("expected second line to be warn") 44 | } 45 | } 46 | 47 | func TestDeferredDebug(t *testing.T) { 48 | oldLogger := Logger 49 | defer func() { Logger = oldLogger }() 50 | 51 | var buf bytes.Buffer 52 | 53 | Logger = xraylog.NewDefaultLogger(&buf, xraylog.LogLevelInfo) 54 | 55 | var called bool 56 | DebugDeferred(func() string { 57 | called = true 58 | return "deferred" 59 | }) 60 | 61 | if called { 62 | t.Error("deferred should not have been called") 63 | } 64 | 65 | if buf.String() != "" { 66 | t.Errorf("unexpected log contents: %s", buf.String()) 67 | } 68 | 69 | Logger = xraylog.NewDefaultLogger(&buf, xraylog.LogLevelDebug) 70 | 71 | DebugDeferred(func() string { 72 | called = true 73 | return "deferred" 74 | }) 75 | 76 | if !called { 77 | t.Error("deferred should have been called") 78 | } 79 | 80 | if !strings.Contains(buf.String(), "[DEBUG] deferred") { 81 | t.Errorf("expected deferred message, got %s", buf.String()) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /internal/plugins/plugin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package plugins 10 | 11 | const ( 12 | // EBServiceName is the key name for metadata of ElasticBeanstalkPlugin. 13 | EBServiceName = "elastic_beanstalk" 14 | 15 | // EC2ServiceName is the key name for metadata of EC2Plugin. 16 | EC2ServiceName = "ec2" 17 | 18 | // ECSServiceName is the key name for metadata of ECSPlugin. 19 | ECSServiceName = "ecs" 20 | ) 21 | 22 | // InstancePluginMetadata points to the PluginMetadata struct. 23 | var InstancePluginMetadata = &PluginMetadata{} 24 | 25 | // PluginMetadata struct contains items to record information 26 | // about the AWS infrastructure hosting the traced application. 27 | type PluginMetadata struct { 28 | 29 | // EC2Metadata records the ec2 instance ID and availability zone. 30 | EC2Metadata *EC2Metadata 31 | 32 | // BeanstalkMetadata records the Elastic Beanstalk 33 | // environment name, version label, and deployment ID. 34 | BeanstalkMetadata *BeanstalkMetadata 35 | 36 | // ECSMetadata records the ECS container ID. 37 | ECSMetadata *ECSMetadata 38 | 39 | // Origin records original service of the segment. 40 | Origin string 41 | } 42 | 43 | // EC2Metadata provides the shape for unmarshalling EC2 metadata. 44 | type EC2Metadata struct { 45 | InstanceID string `json:"instance_id"` 46 | AvailabilityZone string `json:"availability_zone"` 47 | } 48 | 49 | // ECSMetadata provides the shape for unmarshalling 50 | // ECS metadata. 51 | type ECSMetadata struct { 52 | ContainerName string `json:"container"` 53 | } 54 | 55 | // BeanstalkMetadata provides the shape for unmarshalling 56 | // Elastic Beanstalk environment metadata. 57 | type BeanstalkMetadata struct { 58 | Environment string `json:"environment_name"` 59 | VersionLabel string `json:"version_label"` 60 | DeploymentID int `json:"deployment_id"` 61 | } 62 | -------------------------------------------------------------------------------- /lambda/sqs_message_helper.go: -------------------------------------------------------------------------------- 1 | package lambda 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/aws/aws-lambda-go/events" 7 | ) 8 | 9 | func IsSampled(sqsMessge events.SQSMessage) bool { 10 | value, ok := sqsMessge.Attributes["AWSTraceHeader"] 11 | 12 | if !ok { 13 | return false 14 | } 15 | 16 | return strings.Contains(value, "Sampled=1") 17 | } 18 | -------------------------------------------------------------------------------- /lambda/sqs_message_helper_test.go: -------------------------------------------------------------------------------- 1 | package lambda 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/aws/aws-lambda-go/events" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestSQSMessageHelper(t *testing.T) { 11 | testTrue(t, "Root=1-632BB806-bd862e3fe1be46a994272793;Sampled=1") 12 | testTrue(t, "Root=1-5759e988-bd862e3fe1be46a994272793;Sampled=1") 13 | testTrue(t, "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1") 14 | 15 | testFalse(t, "Root=1-632BB806-bd862e3fe1be46a994272793") 16 | testFalse(t, "Root=1-632BB806-bd862e3fe1be46a994272793;Sampled=0") 17 | testFalse(t, "Root=1-5759e988-bd862e3fe1be46a994272793;Sampled=0") 18 | testFalse(t, "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=0") 19 | } 20 | 21 | func testTrue(t *testing.T, header string) { 22 | var sqsMessage events.SQSMessage 23 | sqsMessage.Attributes = make(map[string]string) 24 | sqsMessage.Attributes["AWSTraceHeader"] = header 25 | assert.True(t, IsSampled(sqsMessage)) 26 | } 27 | 28 | func testFalse(t *testing.T, header string) { 29 | var sqsMessage events.SQSMessage 30 | sqsMessage.Attributes = make(map[string]string) 31 | sqsMessage.Attributes["AWSTraceHeader"] = header 32 | assert.False(t, IsSampled(sqsMessage)) 33 | } 34 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | SDK_BASE_FOLDERS=$(shell ls -d */ | grep -v vendor) 2 | GO_VET_CMD=go tool vet --all -shadow 3 | 4 | assets: 5 | rm resources/bindata.go 6 | go-bindata -o resources/bindata.go -pkg resources resources/ 7 | 8 | vet: 9 | ${GO_VET_CMD} ${SDK_BASE_FOLDERS} 10 | 11 | lint: 12 | golint ${SDK_BASE_FOLDERS} 13 | 14 | test:: 15 | go test -cover `go list ./... | grep -v vendor` 16 | 17 | test-with-race: test 18 | go test -cover -race `go list ./... | grep -v vendor` 19 | 20 | fmt: 21 | go fmt `go list ./... | grep -v vendor` 22 | 23 | golangci-lint: 24 | golangci-lint run 25 | 26 | # run all the benchmarks of X-Ray SDK (to minimize logging set loglevel to LogLevelError) 27 | benchmark_sdk: 28 | echo "\`\`\`" > benchmark/benchmark_sdk.md 29 | go test -v -benchmem -run=^$$ -bench=. ./... >> benchmark/benchmark_sdk.md 30 | echo >> benchmark/benchmark_sdk.md 31 | echo "\`\`\`" >> benchmark/benchmark_sdk.md 32 | 33 | # Profiling memory (xray package only and to minimize logging set loglevel to LogLevelError) 34 | benchmark_xray_mem: 35 | go test -benchmem -run=^$$ -bench=. ./xray -memprofile=benchmark/benchmark_xray_mem.profile 36 | 37 | echo "\`\`\`go" > benchmark/benchmark_xray_mem.md 38 | echo >> benchmark/benchmark_xray_mem.md 39 | echo "top" | go tool pprof -sample_index=alloc_objects xray.test benchmark/benchmark_xray_mem.profile >> benchmark/benchmark_xray_mem.md 40 | echo >> benchmark/benchmark_xray_mem.md 41 | echo "top -cum" | go tool pprof -sample_index=alloc_objects xray.test benchmark/benchmark_xray_mem.profile >> benchmark/benchmark_xray_mem.md 42 | echo >> benchmark/benchmark_xray_mem.md 43 | echo "list xray" | go tool pprof -sample_index=alloc_objects xray.test benchmark/benchmark_xray_mem.profile >> benchmark/benchmark_xray_mem.md 44 | echo >> Benchmark/benchmark_xray_mem.md 45 | echo "\`\`\`" >> benchmark/benchmark_xray_mem.md 46 | 47 | rm xray.test 48 | rm benchmark/benchmark_xray_mem.profile 49 | 50 | # profiling cpu (xray package only and to minimize logging set loglevel to LogLevelError) 51 | benchmark_xray_cpu: 52 | go test -benchmem -run=^$$ -bench=. ./xray -cpuprofile=benchmark/benchmark_xray_cpu.profile 53 | 54 | echo "\`\`\`go" > benchmark/benchmark_xray_cpu.md 55 | echo >> benchmark/benchmark_xray_cpu.md 56 | echo "top" | go tool pprof xray.test benchmark/benchmark_xray_cpu.profile >> benchmark/benchmark_xray_cpu.md 57 | echo >> benchmark/benchmark_xray_cpu.md 58 | echo "top -cum" | go tool pprof xray.test benchmark/benchmark_xray_cpu.profile >> benchmark/benchmark_xray_cpu.md 59 | echo >> benchmark/benchmark_xray_cpu.md 60 | echo "list xray" | go tool pprof xray.test benchmark/benchmark_xray_cpu.profile >> benchmark/benchmark_xray_cpu.md 61 | echo >> benchmark/benchmark_xray_cpu.md 62 | echo "\`\`\`" >> benchmark/benchmark_xray_cpu.md 63 | 64 | rm xray.test 65 | rm benchmark/benchmark_xray_cpu.profile -------------------------------------------------------------------------------- /pattern/search_pattern.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | // Package pattern provides a basic pattern matching utility. 10 | // Patterns may contain fixed text, and/or special characters (`*`, `?`). 11 | // `*` represents 0 or more wildcard characters. `?` represents a single wildcard character. 12 | package pattern 13 | 14 | import "strings" 15 | 16 | // WildcardMatchCaseInsensitive returns true if text matches pattern (case-insensitive); returns false otherwise. 17 | func WildcardMatchCaseInsensitive(pattern, text string) bool { 18 | return WildcardMatch(pattern, text, true) 19 | } 20 | 21 | // WildcardMatch returns true if text matches pattern at the given case-sensitivity; returns false otherwise. 22 | func WildcardMatch(pattern, text string, caseInsensitive bool) bool { 23 | patternLen := len(pattern) 24 | textLen := len(text) 25 | if patternLen == 0 { 26 | return textLen == 0 27 | } 28 | 29 | if pattern == "*" { 30 | return true 31 | } 32 | 33 | if caseInsensitive { 34 | pattern = strings.ToLower(pattern) 35 | text = strings.ToLower(text) 36 | } 37 | 38 | i := 0 39 | p := 0 40 | iStar := textLen 41 | pStar := 0 42 | 43 | for i < textLen { 44 | if p < patternLen { 45 | switch pattern[p] { 46 | case text[i]: 47 | i++ 48 | p++ 49 | continue 50 | case '?': 51 | i++ 52 | p++ 53 | continue 54 | case '*': 55 | iStar = i 56 | pStar = p 57 | p++ 58 | continue 59 | } 60 | } 61 | if iStar == textLen { 62 | return false 63 | } 64 | iStar++ 65 | i = iStar 66 | p = pStar + 1 67 | } 68 | 69 | for p < patternLen && pattern[p] == '*' { 70 | p++ 71 | } 72 | 73 | return p == patternLen && i == textLen 74 | } 75 | -------------------------------------------------------------------------------- /resources/DefaultSamplingRules.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "default": { 4 | "fixed_target": 1, 5 | "rate": 0.05 6 | }, 7 | "rules": [ 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /resources/ExampleSamplingRules.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "default": { 4 | "description": "A default rule, as included below, is required in any sampling rules file. (service_name, http_method, and url_path are fixed to '*' for this rule.)", 5 | "fixed_target": 1, 6 | "rate": 0.05 7 | }, 8 | "rules": [ 9 | { 10 | "description": "Example path-based rule below. Rules are evaluated in id-order, the default rule will be used if none match the incoming request. This is a rule for the checkout page.", 11 | "id": "1", 12 | "host": "*", 13 | "http_method": "*", 14 | "url_path": "/checkout", 15 | "fixed_target": 10, 16 | "rate": 0.05 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /sample-apps/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 10 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 11 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 12 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 14 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /sample-apps/http-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24 2 | 3 | WORKDIR /app 4 | COPY . . 5 | ENV GOPROXY=direct 6 | 7 | RUN go mod download 8 | RUN cd sample-apps/http-server/ && go install . 9 | 10 | # Build Golang sample app 11 | RUN cd sample-apps/http-server/ && go build -o application . 12 | 13 | EXPOSE 5000 14 | 15 | # Entrypoint 16 | CMD ["sample-apps/http-server/application"] 17 | -------------------------------------------------------------------------------- /sample-apps/http-server/application.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/aws/aws-sdk-go-v2/config" 10 | "github.com/aws/aws-sdk-go-v2/service/s3" 11 | "github.com/aws/aws-xray-sdk-go/v2/instrumentation/awsv2" 12 | "github.com/aws/aws-xray-sdk-go/v2/xray" 13 | "golang.org/x/net/context/ctxhttp" 14 | ) 15 | 16 | func webServer() { 17 | http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 18 | _, _ = w.Write([]byte("healthcheck")) 19 | })) 20 | 21 | //test http instrumentation 22 | http.Handle("/outgoing-http-call", xray.Handler(xray.NewFixedSegmentNamer("/outgoing-http-call"), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 23 | _, err := ctxhttp.Get(r.Context(), xray.Client(nil), "https://aws.amazon.com") 24 | if err != nil { 25 | log.Println(err) 26 | return 27 | } 28 | _, _ = w.Write([]byte("Tracing http call!")) 29 | }))) 30 | 31 | //test aws sdk instrumentation 32 | http.Handle("/aws-sdk-call", xray.Handler(xray.NewFixedSegmentNamer("/aws-sdk-call"), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 33 | testAWSCalls(r.Context()) 34 | _, _ = w.Write([]byte("Tracing aws sdk call!")) 35 | }))) 36 | 37 | listenAddress := os.Getenv("LISTEN_ADDRESS") 38 | if listenAddress == "" { 39 | listenAddress = "127.0.0.1:5000" 40 | } 41 | _ = http.ListenAndServe(listenAddress, nil) 42 | log.Printf("App is listening on %s !", listenAddress) 43 | } 44 | 45 | func testAWSCalls(ctx context.Context) { 46 | cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-west-2")) 47 | if err != nil { 48 | log.Fatalf("unable to load SDK config, %v", err) 49 | } 50 | // Instrumenting AWS SDK v2 51 | awsv2.AWSV2Instrumentor(&cfg.APIOptions) 52 | // Using the Config value, create the S3 client 53 | svc := s3.NewFromConfig(cfg) 54 | // Build the request with its input parameters 55 | _, err = svc.ListBuckets(ctx, &s3.ListBucketsInput{}) 56 | if err != nil { 57 | log.Fatalf("failed to list buckets, %v", err) 58 | } 59 | 60 | log.Println("Successfully traced aws sdk call") 61 | } 62 | 63 | func main() { 64 | webServer() 65 | } 66 | -------------------------------------------------------------------------------- /sample-apps/http-server/go.mod: -------------------------------------------------------------------------------- 1 | module application.go 2 | 3 | replace github.com/aws/aws-xray-sdk-go/v2 => ../../ 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go-v2/config v1.29.6 7 | github.com/aws/aws-sdk-go-v2/service/s3 v1.77.0 8 | github.com/aws/aws-xray-sdk-go/v2 v2.0.0 9 | golang.org/x/net v0.38.0 10 | ) 11 | 12 | require ( 13 | github.com/andybalholm/brotli v1.1.0 // indirect 14 | github.com/aws/aws-sdk-go-v2 v1.36.1 // indirect 15 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.9 // indirect 16 | github.com/aws/aws-sdk-go-v2/credentials v1.17.59 // indirect 17 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28 // indirect 18 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 // indirect 19 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 // indirect 20 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect 21 | github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.32 // indirect 22 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect 23 | github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.0 // indirect 24 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 // indirect 25 | github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.13 // indirect 26 | github.com/aws/aws-sdk-go-v2/service/sso v1.24.15 // indirect 27 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14 // indirect 28 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.14 // indirect 29 | github.com/aws/smithy-go v1.22.2 // indirect 30 | github.com/klauspost/compress v1.17.6 // indirect 31 | github.com/pkg/errors v0.9.1 // indirect 32 | github.com/valyala/bytebufferpool v1.0.0 // indirect 33 | github.com/valyala/fasthttp v1.52.0 // indirect 34 | golang.org/x/sys v0.31.0 // indirect 35 | golang.org/x/text v0.23.0 // indirect 36 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect 37 | google.golang.org/grpc v1.64.1 // indirect 38 | google.golang.org/protobuf v1.33.0 // indirect 39 | ) 40 | 41 | go 1.24 42 | toolchain go1.24.1 43 | -------------------------------------------------------------------------------- /strategy/ctxmissing/context_missing.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | // Package ctxmissing provides control over 10 | // the behavior of the X-Ray SDK when subsegments 11 | // are created without a provided parent segment. 12 | package ctxmissing 13 | 14 | // Strategy provides an interface for 15 | // implementing context missing strategies. 16 | type Strategy interface { 17 | ContextMissing(v interface{}) 18 | } 19 | -------------------------------------------------------------------------------- /strategy/ctxmissing/ctxmissing_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package ctxmissing 10 | 11 | import ( 12 | "bytes" 13 | "strings" 14 | "testing" 15 | 16 | "github.com/aws/aws-xray-sdk-go/v2/internal/logger" 17 | "github.com/aws/aws-xray-sdk-go/v2/xraylog" 18 | "github.com/stretchr/testify/assert" 19 | ) 20 | 21 | func TestDefaultRuntimeErrorStrategy(t *testing.T) { 22 | defer func() { 23 | if p := recover(); p != nil { 24 | assert.Equal(t, "TestRuntimeError", p.(string)) 25 | } 26 | }() 27 | r := NewDefaultRuntimeErrorStrategy() 28 | r.ContextMissing("TestRuntimeError") 29 | } 30 | 31 | func TestDefaultLogErrorStrategy(t *testing.T) { 32 | oldLogger := logger.Logger 33 | defer func() { logger.Logger = oldLogger }() 34 | 35 | var buf bytes.Buffer 36 | logger.Logger = xraylog.NewDefaultLogger(&buf, xraylog.LogLevelDebug) 37 | 38 | l := NewDefaultLogErrorStrategy() 39 | l.ContextMissing("TestLogError") 40 | assert.True(t, strings.Contains(buf.String(), "Suppressing AWS X-Ray context missing panic: TestLogError")) 41 | } 42 | 43 | func TestDefaultIgnoreErrorStrategy(t *testing.T) { 44 | defer func() { 45 | p := recover() 46 | assert.Equal(t, p, nil) 47 | }() 48 | r := NewDefaultIgnoreErrorStrategy() 49 | r.ContextMissing("TestIgnoreError") 50 | } 51 | -------------------------------------------------------------------------------- /strategy/ctxmissing/default_context_missing.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package ctxmissing 10 | 11 | import "github.com/aws/aws-xray-sdk-go/v2/internal/logger" 12 | 13 | // RuntimeErrorStrategy provides the AWS_XRAY_CONTEXT_MISSING 14 | // environment variable value for enabling the runtime error 15 | // context missing strategy (panic). 16 | var RuntimeErrorStrategy = "RUNTIME_ERROR" 17 | 18 | // LogErrorStrategy provides the AWS_XRAY_CONTEXT_MISSING 19 | // environment variable value for enabling the log error 20 | // context missing strategy. 21 | var LogErrorStrategy = "LOG_ERROR" 22 | 23 | // IgnoreErrorStrategy provides the AWS_XRAY_CONTEXT_MISSING 24 | // environment variable value for enabling the ignore error 25 | // context missing strategy. 26 | var IgnoreErrorStrategy = "IGNORE_ERROR" 27 | 28 | // DefaultRuntimeErrorStrategy implements the 29 | // runtime error context missing strategy. 30 | type DefaultRuntimeErrorStrategy struct{} 31 | 32 | // DefaultLogErrorStrategy implements the 33 | // log error context missing strategy. 34 | type DefaultLogErrorStrategy struct{} 35 | 36 | // DefaultIgnoreErrorStrategy implements the 37 | // ignore error context missing strategy. 38 | type DefaultIgnoreErrorStrategy struct{} 39 | 40 | // NewDefaultRuntimeErrorStrategy initializes 41 | // an instance of DefaultRuntimeErrorStrategy. 42 | func NewDefaultRuntimeErrorStrategy() *DefaultRuntimeErrorStrategy { 43 | return &DefaultRuntimeErrorStrategy{} 44 | } 45 | 46 | // NewDefaultLogErrorStrategy initializes 47 | // an instance of DefaultLogErrorStrategy. 48 | func NewDefaultLogErrorStrategy() *DefaultLogErrorStrategy { 49 | return &DefaultLogErrorStrategy{} 50 | } 51 | 52 | // NewDefaultIgnoreErrorStrategy initializes 53 | // an instance of DefaultIgnoreErrorStrategy. 54 | func NewDefaultIgnoreErrorStrategy() *DefaultIgnoreErrorStrategy { 55 | return &DefaultIgnoreErrorStrategy{} 56 | } 57 | 58 | // ContextMissing panics when the segment context is missing. 59 | func (dr *DefaultRuntimeErrorStrategy) ContextMissing(v interface{}) { 60 | panic(v) 61 | } 62 | 63 | // ContextMissing logs an error message when the 64 | // segment context is missing. 65 | func (dl *DefaultLogErrorStrategy) ContextMissing(v interface{}) { 66 | logger.Errorf("Suppressing AWS X-Ray context missing panic: %v", v) 67 | } 68 | 69 | // ContextMissing ignores an error message when the 70 | // segment context is missing. 71 | func (di *DefaultIgnoreErrorStrategy) ContextMissing(v interface{}) { 72 | // do nothing 73 | } 74 | -------------------------------------------------------------------------------- /strategy/exception/exception_formatting_strategy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package exception 10 | 11 | // FormattingStrategy provides an interface for implementing methods that format errors and exceptions. 12 | type FormattingStrategy interface { 13 | Error(message string) *XRayError 14 | Errorf(formatString string, args ...interface{}) *XRayError 15 | Panic(message string) *XRayError 16 | Panicf(formatString string, args ...interface{}) *XRayError 17 | ExceptionFromError(err error) Exception 18 | } 19 | -------------------------------------------------------------------------------- /strategy/exception/exception_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package exception 10 | 11 | import ( 12 | "errors" 13 | "testing" 14 | 15 | "github.com/aws/aws-xray-sdk-go/v2/utils/awserr" 16 | "github.com/stretchr/testify/assert" 17 | ) 18 | 19 | func TestMultiErrorReturnStringFormat(t *testing.T) { 20 | var err MultiError 21 | err = append(err, errors.New("error one")) 22 | err = append(err, errors.New("error two")) 23 | assert.Equal(t, "2 errors occurred:\n* error one\n* error two\n", err.Error()) 24 | } 25 | 26 | func TestDefaultFormattingStrategyWithInvalidFrameCount(t *testing.T) { 27 | dss, e := NewDefaultFormattingStrategyWithDefinedErrorFrameCount(-1) 28 | ds, err := NewDefaultFormattingStrategyWithDefinedErrorFrameCount(33) 29 | assert.Nil(t, dss) 30 | assert.Nil(t, ds) 31 | assert.Error(t, e, "frameCount must be a non-negative integer and less than 32") 32 | assert.Error(t, err, "frameCount must be a non-negative integer and less than 32") 33 | } 34 | 35 | func TestNewDefaultFormattingStrategyWithValidFrameCount(t *testing.T) { 36 | dss, e := NewDefaultFormattingStrategyWithDefinedErrorFrameCount(10) 37 | assert.Nil(t, e) 38 | assert.Equal(t, 10, dss.FrameCount) 39 | } 40 | 41 | func TestError(t *testing.T) { 42 | defs, _ := NewDefaultFormattingStrategy() 43 | 44 | err := defs.Error("Test") 45 | stack := convertStack(err.StackTrace()) 46 | 47 | assert.Equal(t, "Test", err.Error()) 48 | assert.Equal(t, "error", err.Type) 49 | assert.Equal(t, "TestError", stack[0].Label) 50 | } 51 | 52 | func TestErrorf(t *testing.T) { 53 | defs, _ := NewDefaultFormattingStrategy() 54 | 55 | err := defs.Errorf("Test") 56 | stack := convertStack(err.StackTrace()) 57 | 58 | assert.Equal(t, "Test", err.Error()) 59 | assert.Equal(t, "error", err.Type) 60 | assert.Equal(t, "TestErrorf", stack[0].Label) 61 | } 62 | 63 | func TestPanic(t *testing.T) { 64 | defs, _ := NewDefaultFormattingStrategy() 65 | 66 | var err *XRayError 67 | func() { 68 | defer func() { 69 | err = defs.Panic(recover().(string)) 70 | }() 71 | panic("Test") 72 | }() 73 | stack := convertStack(err.StackTrace()) 74 | 75 | assert.Equal(t, "Test", err.Error()) 76 | assert.Equal(t, "panic", err.Type) 77 | assert.Equal(t, "TestPanic.func1", stack[0].Label) 78 | assert.Equal(t, "TestPanic", stack[1].Label) 79 | } 80 | 81 | func TestPanicf(t *testing.T) { 82 | defs, _ := NewDefaultFormattingStrategy() 83 | 84 | var err *XRayError 85 | func() { 86 | defer func() { 87 | err = defs.Panicf("%v", recover()) 88 | }() 89 | panic("Test") 90 | }() 91 | stack := convertStack(err.StackTrace()) 92 | 93 | assert.Equal(t, "Test", err.Error()) 94 | assert.Equal(t, "panic", err.Type) 95 | assert.Equal(t, "TestPanicf.func1", stack[0].Label) 96 | assert.Equal(t, "TestPanicf", stack[1].Label) 97 | } 98 | 99 | func TestExceptionFromError(t *testing.T) { 100 | defaultStrategy := &DefaultFormattingStrategy{} 101 | 102 | err := defaultStrategy.ExceptionFromError(errors.New("new error")) 103 | 104 | assert.NotNil(t, err.ID) 105 | assert.Equal(t, "new error", err.Message) 106 | assert.Equal(t, "errors.errorString", err.Type) 107 | } 108 | 109 | func TestExceptionFromErrorRequestFailure(t *testing.T) { 110 | defaultStrategy := &DefaultFormattingStrategy{} 111 | reqErr := awserr.NewRequestFailure(awserr.New("error code", "error message", errors.New("new error")), 400, "1234") 112 | 113 | err := defaultStrategy.ExceptionFromError(reqErr) 114 | 115 | assert.NotNil(t, err.ID) 116 | assert.Contains(t, err.Message, "new error") 117 | assert.Contains(t, err.Message, "1234") 118 | assert.Equal(t, "awserr.requestError", err.Type) 119 | assert.Equal(t, true, err.Remote) 120 | } 121 | 122 | func TestExceptionFromErrorXRayError(t *testing.T) { 123 | defaultStrategy := &DefaultFormattingStrategy{} 124 | xRayErr := defaultStrategy.Error("new XRayError") 125 | 126 | err := defaultStrategy.ExceptionFromError(xRayErr) 127 | 128 | assert.NotNil(t, err.ID) 129 | assert.Equal(t, "new XRayError", err.Message) 130 | assert.Equal(t, "error", err.Type) 131 | } 132 | 133 | // Benchmarks 134 | func BenchmarkDefaultFormattingStrategy_Error(b *testing.B) { 135 | defs, _ := NewDefaultFormattingStrategy() 136 | err := defs.Error("Test") 137 | 138 | for i := 0; i < b.N; i++ { 139 | convertStack(err.StackTrace()) 140 | } 141 | } 142 | 143 | func BenchmarkDefaultFormattingStrategy_ExceptionFromError(b *testing.B) { 144 | defaultStrategy := &DefaultFormattingStrategy{} 145 | err := "new error" 146 | 147 | for i := 0; i < b.N; i++ { 148 | defaultStrategy.ExceptionFromError(errors.New(err)) 149 | } 150 | } 151 | 152 | func BenchmarkDefaultFormattingStrategy_Panic(b *testing.B) { 153 | defs, _ := NewDefaultFormattingStrategy() 154 | 155 | for i := 0; i < b.N; i++ { 156 | var err *XRayError 157 | func() { 158 | defer func() { 159 | err = defs.Panic(recover().(string)) 160 | }() 161 | panic("Test") 162 | }() 163 | convertStack(err.StackTrace()) 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /strategy/exception/xray_error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package exception 10 | 11 | // XRayError records error type, message, 12 | // and a slice of stack frame pointers. 13 | type XRayError struct { 14 | Type string 15 | Message string 16 | Stack []uintptr 17 | } 18 | 19 | // Error returns the value of error message. 20 | func (e *XRayError) Error() string { 21 | return e.Message 22 | } 23 | 24 | // StackTrace returns a slice of integer pointers. 25 | func (e *XRayError) StackTrace() []uintptr { 26 | return e.Stack 27 | } 28 | -------------------------------------------------------------------------------- /strategy/sampling/localized.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package sampling 10 | 11 | import ( 12 | "github.com/aws/aws-xray-sdk-go/v2/internal/logger" 13 | "github.com/aws/aws-xray-sdk-go/v2/resources" 14 | ) 15 | 16 | // LocalizedStrategy makes trace sampling decisions based on 17 | // a set of rules provided in a local JSON file. Trace sampling 18 | // decisions are made by the root node in the trace. If a 19 | // sampling decision is made by the root service, it will be passed 20 | // to downstream services through the trace header. 21 | type LocalizedStrategy struct { 22 | manifest *RuleManifest 23 | } 24 | 25 | // NewLocalizedStrategy initializes an instance of LocalizedStrategy 26 | // with the default trace sampling rules. The default rules sample 27 | // the first request per second, and 5% of requests thereafter. 28 | func NewLocalizedStrategy() (*LocalizedStrategy, error) { 29 | bytes, err := resources.Asset("resources/DefaultSamplingRules.json") 30 | if err != nil { 31 | return nil, err 32 | } 33 | manifest, err := ManifestFromJSONBytes(bytes) 34 | if err != nil { 35 | return nil, err 36 | } 37 | return &LocalizedStrategy{manifest: manifest}, nil 38 | } 39 | 40 | // NewLocalizedStrategyFromFilePath initializes an instance of 41 | // LocalizedStrategy using a custom ruleset found at the filepath fp. 42 | func NewLocalizedStrategyFromFilePath(fp string) (*LocalizedStrategy, error) { 43 | manifest, err := ManifestFromFilePath(fp) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return &LocalizedStrategy{manifest: manifest}, nil 48 | } 49 | 50 | // NewLocalizedStrategyFromJSONBytes initializes an instance of 51 | // LocalizedStrategy using a custom ruleset provided in the json bytes b. 52 | func NewLocalizedStrategyFromJSONBytes(b []byte) (*LocalizedStrategy, error) { 53 | manifest, err := ManifestFromJSONBytes(b) 54 | if err != nil { 55 | return nil, err 56 | } 57 | return &LocalizedStrategy{manifest: manifest}, nil 58 | } 59 | 60 | // ShouldTrace consults the LocalizedStrategy's rule set to determine 61 | // if the given request should be traced or not. 62 | func (lss *LocalizedStrategy) ShouldTrace(rq *Request) *Decision { 63 | logger.Debugf("Determining ShouldTrace decision for:\n\thost: %s\n\tpath: %s\n\tmethod: %s", rq.Host, rq.URL, rq.Method) 64 | if nil != lss.manifest.Rules { 65 | for _, r := range lss.manifest.Rules { 66 | if r.AppliesTo(rq.Host, rq.URL, rq.Method) { 67 | logger.Debugf("Applicable rule:\n\tfixed_target: %d\n\trate: %f\n\thost: %s\n\turl_path: %s\n\thttp_method: %s", r.FixedTarget, r.Rate, r.Host, r.URLPath, r.HTTPMethod) 68 | return r.Sample() 69 | } 70 | } 71 | } 72 | logger.Debugf("Default rule applies:\n\tfixed_target: %d\n\trate: %f", lss.manifest.Default.FixedTarget, lss.manifest.Default.Rate) 73 | return lss.manifest.Default.Sample() 74 | } 75 | -------------------------------------------------------------------------------- /strategy/sampling/proxy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package sampling 10 | 11 | import ( 12 | "bytes" 13 | "encoding/json" 14 | "fmt" 15 | "net/http" 16 | 17 | "github.com/aws/aws-xray-sdk-go/v2/daemoncfg" 18 | "github.com/aws/aws-xray-sdk-go/v2/internal/logger" 19 | ) 20 | 21 | // proxy is an implementation of svcProxy that forwards requests to the XRay daemon 22 | type proxy struct { 23 | // XRay client for sending unsigned proxied requests to the daemon 24 | xray *xrayClient 25 | } 26 | 27 | // NewProxy returns a Proxy 28 | func newProxy(d *daemoncfg.DaemonEndpoints) (svcProxy, error) { 29 | if d == nil { 30 | d = daemoncfg.GetDaemonEndpoints() 31 | } 32 | logger.Infof("X-Ray proxy using address : %v", d.TCPAddr.String()) 33 | url := "http://" + d.TCPAddr.String() 34 | 35 | // Construct resolved URLs for getSamplingRules and getSamplingTargets API calls. 36 | samplingRulesURL := url + "/GetSamplingRules" 37 | samplingTargetsURL := url + "/SamplingTargets" 38 | 39 | p := &proxy{ 40 | xray: &xrayClient{ 41 | httpClient: &http.Client{}, 42 | samplingRulesURL: samplingRulesURL, 43 | samplingTargetsURL: samplingTargetsURL, 44 | }, 45 | } 46 | 47 | return p, nil 48 | } 49 | 50 | type xrayClient struct { 51 | // HTTP client for sending sampling requests to the collector. 52 | httpClient *http.Client 53 | 54 | // Resolved URL to call getSamplingRules API. 55 | samplingRulesURL string 56 | 57 | // Resolved URL to call getSamplingTargets API. 58 | samplingTargetsURL string 59 | } 60 | 61 | // getSamplingRules calls the collector(aws proxy enabled) for sampling rules. 62 | func (c *xrayClient) getSamplingRules() (*GetSamplingRulesOutput, error) { 63 | emptySamplingRulesInputJSON := []byte(`{"NextToken": null}`) 64 | 65 | body := bytes.NewReader(emptySamplingRulesInputJSON) 66 | 67 | req, err := http.NewRequest(http.MethodPost, c.samplingRulesURL, body) 68 | if err != nil { 69 | return nil, fmt.Errorf("unable to retrieve sampling rules, error on http request: %w", err) 70 | } 71 | 72 | output, err := c.httpClient.Do(req) 73 | if err != nil { 74 | return nil, fmt.Errorf("xray client: unable to retrieve sampling rules, error on http request: %w", err) 75 | } 76 | defer output.Body.Close() 77 | 78 | if output.StatusCode != http.StatusOK { 79 | return nil, fmt.Errorf("xray client: unable to retrieve sampling rules, expected response status code 200, got: %d", output.StatusCode) 80 | } 81 | 82 | var samplingRulesOutput *GetSamplingRulesOutput 83 | if err := json.NewDecoder(output.Body).Decode(&samplingRulesOutput); err != nil { 84 | return nil, fmt.Errorf("xray client: unable to retrieve sampling rules, unable to unmarshal the response body: %w", err) 85 | } 86 | 87 | return samplingRulesOutput, nil 88 | } 89 | 90 | // getSamplingTargets calls the Daemon (aws proxy enabled) for sampling targets. 91 | func (c *xrayClient) getSamplingTargets(s []*SamplingStatisticsDocument) (*GetSamplingTargetsOutput, error) { 92 | statistics := GetSamplingTargetsInput{ 93 | SamplingStatisticsDocuments: s, 94 | } 95 | 96 | statisticsByte, err := json.Marshal(statistics) 97 | if err != nil { 98 | return nil, err 99 | } 100 | body := bytes.NewReader(statisticsByte) 101 | 102 | req, err := http.NewRequest(http.MethodPost, c.samplingTargetsURL, body) 103 | if err != nil { 104 | return nil, fmt.Errorf("xray client: failed to create http request: %w", err) 105 | } 106 | 107 | output, err := c.httpClient.Do(req) 108 | if err != nil { 109 | return nil, fmt.Errorf("xray client: unable to retrieve sampling targets, error on http request: %w", err) 110 | } 111 | defer output.Body.Close() 112 | 113 | if output.StatusCode != http.StatusOK { 114 | return nil, fmt.Errorf("xray client: unable to retrieve sampling targets, expected response status code 200, got: %d", output.StatusCode) 115 | } 116 | 117 | var samplingTargetsOutput *GetSamplingTargetsOutput 118 | if err := json.NewDecoder(output.Body).Decode(&samplingTargetsOutput); err != nil { 119 | return nil, fmt.Errorf("xray client: unable to retrieve sampling targets, unable to unmarshal the response body: %w", err) 120 | } 121 | 122 | return samplingTargetsOutput, nil 123 | } 124 | 125 | // GetSamplingTargets calls the XRay daemon for sampling targets 126 | func (p *proxy) GetSamplingTargets(s []*SamplingStatisticsDocument) (*GetSamplingTargetsOutput, error) { 127 | output, err := p.xray.getSamplingTargets(s) 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | return output, nil 133 | } 134 | 135 | // GetSamplingRules calls the XRay daemon for sampling rules 136 | func (p *proxy) GetSamplingRules() ([]*SamplingRuleRecord, error) { 137 | output, err := p.xray.getSamplingRules() 138 | if err != nil { 139 | return nil, err 140 | } 141 | 142 | rules := output.SamplingRuleRecords 143 | 144 | return rules, nil 145 | } 146 | -------------------------------------------------------------------------------- /strategy/sampling/reservoir.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package sampling 10 | 11 | import "github.com/aws/aws-xray-sdk-go/v2/utils" 12 | 13 | // Reservoirs allow a specified (`perSecond`) amount of `Take()`s per second. 14 | 15 | // reservoir is a set of properties common to all reservoirs 16 | type reservoir struct { 17 | // Total size of reservoir 18 | capacity int64 19 | 20 | // Reservoir consumption for current epoch 21 | used int64 22 | 23 | // Unix epoch. Reservoir usage is reset every second. 24 | currentEpoch int64 25 | } 26 | 27 | // CentralizedReservoir is a reservoir distributed among all running instances of the SDK 28 | type CentralizedReservoir struct { 29 | // Quota assigned to client 30 | quota int64 31 | 32 | // Quota refresh timestamp 33 | refreshedAt int64 34 | 35 | // Quota expiration timestamp 36 | expiresAt int64 37 | 38 | // Polling interval for quota 39 | interval int64 40 | 41 | // True if reservoir has been borrowed from this epoch 42 | borrowed bool 43 | 44 | // Common reservoir properties 45 | *reservoir 46 | } 47 | 48 | // expired returns true if current time is past expiration timestamp. False otherwise. 49 | func (r *CentralizedReservoir) expired(now int64) bool { 50 | return now > r.expiresAt 51 | } 52 | 53 | // borrow returns true if the reservoir has not been borrowed from this epoch 54 | func (r *CentralizedReservoir) borrow(now int64) bool { 55 | if now != r.currentEpoch { 56 | r.reset(now) 57 | } 58 | 59 | s := r.borrowed 60 | r.borrowed = true 61 | 62 | return !s && r.reservoir.capacity != 0 63 | } 64 | 65 | // Take consumes quota from reservoir, if any remains, and returns true. False otherwise. 66 | func (r *CentralizedReservoir) Take(now int64) bool { 67 | if now != r.currentEpoch { 68 | r.reset(now) 69 | } 70 | 71 | // Consume from quota, if available 72 | if r.quota > r.used { 73 | r.used++ 74 | 75 | return true 76 | } 77 | 78 | return false 79 | } 80 | 81 | func (r *CentralizedReservoir) reset(now int64) { 82 | r.currentEpoch, r.used, r.borrowed = now, 0, false 83 | } 84 | 85 | // Reservoir is a reservoir local to the running instance of the SDK 86 | type Reservoir struct { 87 | // Provides system time 88 | clock utils.Clock 89 | 90 | *reservoir 91 | } 92 | 93 | // Take attempts to consume a unit from the local reservoir. Returns true if unit taken, false otherwise. 94 | func (r *Reservoir) Take() bool { 95 | // Reset counters if new second 96 | if now := r.clock.Now().Unix(); now != r.currentEpoch { 97 | r.used = 0 98 | r.currentEpoch = now 99 | } 100 | 101 | // Take from reservoir, if available 102 | if r.used >= r.capacity { 103 | return false 104 | } 105 | 106 | // Increment reservoir usage 107 | r.used++ 108 | 109 | return true 110 | } 111 | -------------------------------------------------------------------------------- /strategy/sampling/reservoir_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package sampling 10 | 11 | import ( 12 | "math" 13 | "testing" 14 | 15 | "github.com/aws/aws-xray-sdk-go/v2/utils" 16 | "github.com/stretchr/testify/assert" 17 | ) 18 | 19 | const Interval = 100 20 | 21 | func takeOverTime(r *Reservoir, millis int) int { 22 | taken := 0 23 | for i := 0; i < millis/Interval; i++ { 24 | if r.Take() { 25 | taken++ 26 | } 27 | r.clock.Increment(0, 1e6*Interval) 28 | } 29 | return taken 30 | } 31 | 32 | const TestDuration = 1500 33 | 34 | // Asserts consumption from reservoir once per second 35 | func TestOnePerSecond(t *testing.T) { 36 | clock := &utils.MockClock{} 37 | cap := 1 38 | res := &Reservoir{ 39 | clock: clock, 40 | reservoir: &reservoir{ 41 | capacity: int64(cap), 42 | }, 43 | } 44 | taken := takeOverTime(res, TestDuration) 45 | assert.True(t, int(math.Ceil(TestDuration/1000.0)) == taken) 46 | } 47 | 48 | // Asserts consumption from reservoir ten times per second 49 | func TestTenPerSecond(t *testing.T) { 50 | clock := &utils.MockClock{} 51 | cap := 10 52 | res := &Reservoir{ 53 | clock: clock, 54 | reservoir: &reservoir{ 55 | capacity: int64(cap), 56 | }, 57 | } 58 | taken := takeOverTime(res, TestDuration) 59 | assert.True(t, int(math.Ceil(float64(TestDuration*cap)/1000.0)) == taken) 60 | } 61 | 62 | func TestTakeQuotaAvailable(t *testing.T) { 63 | capacity := int64(100) 64 | used := int64(0) 65 | quota := int64(9) 66 | 67 | clock := &utils.MockClock{ 68 | NowTime: 1500000000, 69 | } 70 | 71 | r := &CentralizedReservoir{ 72 | quota: quota, 73 | reservoir: &reservoir{ 74 | capacity: capacity, 75 | used: used, 76 | currentEpoch: clock.Now().Unix(), 77 | }, 78 | } 79 | 80 | s := r.Take(clock.Now().Unix()) 81 | assert.Equal(t, true, s) 82 | assert.Equal(t, int64(1), r.used) 83 | } 84 | 85 | func TestTakeQuotaUnavailable(t *testing.T) { 86 | capacity := int64(100) 87 | used := int64(100) 88 | quota := int64(9) 89 | 90 | clock := &utils.MockClock{ 91 | NowTime: 1500000000, 92 | } 93 | 94 | r := &CentralizedReservoir{ 95 | quota: quota, 96 | reservoir: &reservoir{ 97 | capacity: capacity, 98 | used: used, 99 | currentEpoch: clock.Now().Unix(), 100 | }, 101 | } 102 | 103 | s := r.Take(clock.Now().Unix()) 104 | assert.Equal(t, false, s) 105 | assert.Equal(t, int64(100), r.used) 106 | } 107 | 108 | func TestExpiredReservoir(t *testing.T) { 109 | clock := &utils.MockClock{ 110 | NowTime: 1500000001, 111 | } 112 | 113 | r := &CentralizedReservoir{ 114 | expiresAt: 1500000000, 115 | } 116 | 117 | expired := r.expired(clock.Now().Unix()) 118 | 119 | assert.Equal(t, true, expired) 120 | } 121 | 122 | // Assert that the borrow flag is reset every second 123 | func TestBorrowFlagReset(t *testing.T) { 124 | clock := &utils.MockClock{ 125 | NowTime: 1500000000, 126 | } 127 | 128 | r := &CentralizedReservoir{ 129 | reservoir: &reservoir{ 130 | capacity: 10, 131 | }, 132 | } 133 | 134 | s := r.borrow(clock.Now().Unix()) 135 | assert.True(t, s) 136 | 137 | s = r.borrow(clock.Now().Unix()) 138 | assert.False(t, s) 139 | 140 | // Increment clock by 1 141 | clock = &utils.MockClock{ 142 | NowTime: 1500000001, 143 | } 144 | 145 | // Reset borrow flag 146 | r.Take(clock.Now().Unix()) 147 | 148 | s = r.borrow(clock.Now().Unix()) 149 | assert.True(t, s) 150 | } 151 | 152 | // Assert that the reservoir does not allow borrowing if the reservoir capacity 153 | // is zero. 154 | func TestBorrowZeroCapacity(t *testing.T) { 155 | clock := &utils.MockClock{ 156 | NowTime: 1500000000, 157 | } 158 | 159 | r := &CentralizedReservoir{ 160 | reservoir: &reservoir{ 161 | capacity: 0, 162 | }, 163 | } 164 | 165 | s := r.borrow(clock.Now().Unix()) 166 | assert.False(t, s) 167 | } 168 | 169 | func TestResetQuotaUsageRotation(t *testing.T) { 170 | capacity := int64(100) 171 | used := int64(0) 172 | quota := int64(5) 173 | 174 | clock := &utils.MockClock{ 175 | NowTime: 1500000000, 176 | } 177 | 178 | r := &CentralizedReservoir{ 179 | quota: quota, 180 | reservoir: &reservoir{ 181 | capacity: capacity, 182 | used: used, 183 | currentEpoch: clock.Now().Unix(), 184 | }, 185 | } 186 | 187 | // Consume quota for second 188 | for i := 0; i < 5; i++ { 189 | taken := r.Take(clock.Now().Unix()) 190 | assert.Equal(t, true, taken) 191 | assert.Equal(t, int64(i+1), r.used) 192 | } 193 | 194 | // Take() should be false since no unused quota left 195 | taken := r.Take(clock.Now().Unix()) 196 | assert.Equal(t, false, taken) 197 | assert.Equal(t, int64(5), r.used) 198 | 199 | // Increment epoch to reset unused quota 200 | clock = &utils.MockClock{ 201 | NowTime: 1500000001, 202 | } 203 | 204 | // Take() should be true since ununsed quota is available 205 | taken = r.Take(clock.Now().Unix()) 206 | assert.Equal(t, int64(1500000001), r.currentEpoch) 207 | assert.Equal(t, true, taken) 208 | assert.Equal(t, int64(1), r.used) 209 | } 210 | 211 | func TestResetReservoirUsageRotation(t *testing.T) { 212 | capacity := int64(5) 213 | used := int64(0) 214 | 215 | clock := &utils.MockClock{ 216 | NowTime: 1500000000, 217 | } 218 | 219 | r := &Reservoir{ 220 | clock: clock, 221 | reservoir: &reservoir{ 222 | capacity: capacity, 223 | used: used, 224 | currentEpoch: clock.Now().Unix(), 225 | }, 226 | } 227 | 228 | // Consume reservoir for second 229 | for i := 0; i < 5; i++ { 230 | taken := r.Take() 231 | assert.Equal(t, true, taken) 232 | assert.Equal(t, int64(i+1), r.used) 233 | } 234 | 235 | // Take() should be false since no reservoir left 236 | taken := r.Take() 237 | assert.Equal(t, false, taken) 238 | assert.Equal(t, int64(5), r.used) 239 | 240 | // Increment epoch to reset reservoir 241 | clock = &utils.MockClock{ 242 | NowTime: 1500000001, 243 | } 244 | r.clock = clock 245 | 246 | // Take() should be true since reservoir is available 247 | taken = r.Take() 248 | assert.Equal(t, int64(1500000001), r.currentEpoch) 249 | assert.Equal(t, true, taken) 250 | assert.Equal(t, int64(1), r.used) 251 | } 252 | -------------------------------------------------------------------------------- /strategy/sampling/sampling_attributes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package sampling 10 | 11 | // Decision contains sampling decision and the rule matched for an incoming request 12 | type Decision struct { 13 | Sample bool 14 | Rule *string 15 | } 16 | 17 | // Request represents parameters used to make a sampling decision. 18 | type Request struct { 19 | Host string 20 | Method string 21 | URL string 22 | ServiceName string 23 | ServiceType string 24 | } 25 | -------------------------------------------------------------------------------- /strategy/sampling/sampling_rule_manifest.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package sampling 10 | 11 | import ( 12 | "encoding/json" 13 | "errors" 14 | "fmt" 15 | "io/ioutil" 16 | "time" 17 | 18 | "github.com/aws/aws-xray-sdk-go/v2/utils" 19 | ) 20 | 21 | // RuleManifest represents a full sampling ruleset, with a list of 22 | // custom rules and default values for incoming requests that do 23 | // not match any of the provided rules. 24 | type RuleManifest struct { 25 | Version int `json:"version"` 26 | Default *Rule `json:"default"` 27 | Rules []*Rule `json:"rules"` 28 | } 29 | 30 | // ManifestFromFilePath creates a sampling ruleset from a given filepath fp. 31 | func ManifestFromFilePath(fp string) (*RuleManifest, error) { 32 | b, err := ioutil.ReadFile(fp) 33 | if err == nil { 34 | return ManifestFromJSONBytes(b) 35 | } 36 | 37 | return nil, err 38 | } 39 | 40 | // ManifestFromJSONBytes creates a sampling ruleset from given JSON bytes b. 41 | func ManifestFromJSONBytes(b []byte) (*RuleManifest, error) { 42 | s := &RuleManifest{} 43 | err := json.Unmarshal(b, s) 44 | if err != nil { 45 | return nil, err 46 | } 47 | err = processManifest(s) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | initSamplingRules(s) 53 | 54 | return s, nil 55 | } 56 | 57 | // Init local reservoir and add random number generator 58 | func initSamplingRules(srm *RuleManifest) { 59 | // Init user-defined rules 60 | for _, r := range srm.Rules { 61 | r.rand = &utils.DefaultRand{} 62 | 63 | r.reservoir = &Reservoir{ 64 | clock: &utils.DefaultClock{}, 65 | reservoir: &reservoir{ 66 | capacity: r.FixedTarget, 67 | used: 0, 68 | currentEpoch: time.Now().Unix(), 69 | }, 70 | } 71 | } 72 | 73 | // Init default rule 74 | srm.Default.rand = &utils.DefaultRand{} 75 | 76 | srm.Default.reservoir = &Reservoir{ 77 | clock: &utils.DefaultClock{}, 78 | reservoir: &reservoir{ 79 | capacity: srm.Default.FixedTarget, 80 | used: 0, 81 | currentEpoch: time.Now().Unix(), 82 | }, 83 | } 84 | } 85 | 86 | // processManifest returns the provided manifest if valid, or an error if the provided manifest is invalid. 87 | func processManifest(srm *RuleManifest) error { 88 | if srm == nil { 89 | return errors.New("sampling rule manifest must not be nil") 90 | } 91 | if srm.Version != 1 && srm.Version != 2 { 92 | return fmt.Errorf("sampling rule manifest version %d not supported", srm.Version) 93 | } 94 | if srm.Default == nil { 95 | return errors.New("sampling rule manifest must include a default rule") 96 | } 97 | if srm.Default.URLPath != "" || srm.Default.ServiceName != "" || srm.Default.HTTPMethod != "" { 98 | return errors.New("the default rule must not specify values for url_path, service_name, or http_method") 99 | } 100 | if srm.Default.FixedTarget < 0 || srm.Default.Rate < 0 { 101 | return errors.New("the default rule must specify non-negative values for fixed_target and rate") 102 | } 103 | 104 | c := &utils.DefaultClock{} 105 | 106 | srm.Default.reservoir = &Reservoir{ 107 | clock: c, 108 | reservoir: &reservoir{ 109 | capacity: srm.Default.FixedTarget, 110 | }, 111 | } 112 | 113 | if srm.Rules != nil { 114 | for _, r := range srm.Rules { 115 | 116 | if srm.Version == 1 { 117 | if err := validateVersion1(r); err != nil { 118 | return err 119 | } 120 | r.Host = r.ServiceName // V1 sampling rule contains service name and not host 121 | r.ServiceName = "" 122 | } 123 | 124 | if srm.Version == 2 { 125 | if err := validateVersion2(r); err != nil { 126 | return err 127 | } 128 | } 129 | 130 | r.reservoir = &Reservoir{ 131 | clock: c, 132 | reservoir: &reservoir{ 133 | capacity: r.FixedTarget, 134 | }, 135 | } 136 | } 137 | } 138 | return nil 139 | } 140 | 141 | func validateVersion2(rule *Rule) error { 142 | if rule.FixedTarget < 0 || rule.Rate < 0 { 143 | return errors.New("all rules must have non-negative values for fixed_target and rate") 144 | } 145 | if rule.ServiceName != "" || rule.Host == "" || rule.HTTPMethod == "" || rule.URLPath == "" { 146 | return errors.New("all non-default rules must have values for url_path, host, and http_method") 147 | } 148 | return nil 149 | } 150 | 151 | func validateVersion1(rule *Rule) error { 152 | if rule.FixedTarget < 0 || rule.Rate < 0 { 153 | return errors.New("all rules must have non-negative values for fixed_target and rate") 154 | } 155 | if rule.Host != "" || rule.ServiceName == "" || rule.HTTPMethod == "" || rule.URLPath == "" { 156 | return errors.New("all non-default rules must have values for url_path, service_name, and http_method") 157 | } 158 | return nil 159 | } 160 | -------------------------------------------------------------------------------- /strategy/sampling/sampling_strategy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package sampling 10 | 11 | // Strategy provides an interface for implementing trace sampling strategies. 12 | type Strategy interface { 13 | ShouldTrace(request *Request) *Decision 14 | } 15 | -------------------------------------------------------------------------------- /strategy/sampling/sampling_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package sampling 10 | 11 | import ( 12 | "path/filepath" 13 | "testing" 14 | 15 | "github.com/stretchr/testify/assert" 16 | ) 17 | 18 | func TestNewLocalizedStrategy(t *testing.T) { 19 | ss, err := NewLocalizedStrategy() 20 | assert.NotNil(t, ss) 21 | assert.Nil(t, err) 22 | } 23 | 24 | func TestNewLocalizedStrategyFromFilePath1(t *testing.T) { // V1 sampling 25 | testFile, err := filepath.Abs(filepath.Join("testdata", "rule-v1-sampling.json")) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | ss, err := NewLocalizedStrategyFromFilePath(testFile) 30 | assert.NotNil(t, ss) 31 | assert.Equal(t, 1, ss.manifest.Version) 32 | assert.Equal(t, 1, len(ss.manifest.Rules)) 33 | assert.Equal(t, "", ss.manifest.Rules[0].ServiceName) 34 | assert.Equal(t, "*", ss.manifest.Rules[0].Host) // always host set for V1 and V2 sampling rule 35 | assert.Equal(t, "*", ss.manifest.Rules[0].HTTPMethod) 36 | assert.Equal(t, "/checkout", ss.manifest.Rules[0].URLPath) 37 | assert.Equal(t, int64(10), ss.manifest.Rules[0].FixedTarget) 38 | assert.Equal(t, 0.05, ss.manifest.Rules[0].Rate) 39 | 40 | assert.Nil(t, err) 41 | } 42 | 43 | func TestNewLocalizedStrategyFromFilePath2(t *testing.T) { // V2 sampling 44 | testFile, err := filepath.Abs(filepath.Join("testdata", "rule-v2-sampling.json")) 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | ss, err := NewLocalizedStrategyFromFilePath(testFile) 49 | assert.NotNil(t, ss) 50 | assert.Equal(t, 2, ss.manifest.Version) 51 | assert.Equal(t, 1, len(ss.manifest.Rules)) 52 | assert.Equal(t, "", ss.manifest.Rules[0].ServiceName) 53 | assert.Equal(t, "*", ss.manifest.Rules[0].Host) 54 | assert.Equal(t, "*", ss.manifest.Rules[0].HTTPMethod) 55 | assert.Equal(t, "/checkout", ss.manifest.Rules[0].URLPath) 56 | assert.Equal(t, int64(10), ss.manifest.Rules[0].FixedTarget) 57 | assert.Equal(t, 0.05, ss.manifest.Rules[0].Rate) 58 | 59 | assert.Nil(t, err) 60 | } 61 | 62 | func TestNewLocalizedStrategyFromFilePathInvalidRulesV1(t *testing.T) { // V1 contains host 63 | testFile, err := filepath.Abs(filepath.Join("testdata", "rule-v1-contains-host.json")) 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | ss, err := NewLocalizedStrategyFromFilePath(testFile) 68 | assert.Nil(t, ss) 69 | assert.NotNil(t, err) 70 | } 71 | 72 | func TestNewLocalizedStrategyFromFilePathInvalidRulesV2(t *testing.T) { // V2 contains service_name 73 | testFile, err := filepath.Abs(filepath.Join("testdata", "rule-v2-contains-service-name.json")) 74 | if err != nil { 75 | t.Fatal(err) 76 | } 77 | ss, err := NewLocalizedStrategyFromFilePath(testFile) 78 | assert.Nil(t, ss) 79 | assert.NotNil(t, err) 80 | } 81 | 82 | func TestNewLocalizedStrategyFromFilePathWithInvalidJSON(t *testing.T) { // Test V1 sampling rule 83 | testFile, err := filepath.Abs(filepath.Join("testdata", "rule-v1-invalid.json")) 84 | if err != nil { 85 | t.Fatal(err) 86 | } 87 | ss, err := NewLocalizedStrategyFromFilePath(testFile) 88 | assert.Nil(t, ss) 89 | assert.NotNil(t, err) 90 | } 91 | 92 | func TestNewLocalizedStrategyFromJSONBytes(t *testing.T) { 93 | ruleBytes := []byte(`{ 94 | "version": 1, 95 | "default": { 96 | "fixed_target": 1, 97 | "rate": 0.05 98 | }, 99 | "rules": [ 100 | ] 101 | }`) 102 | ss, err := NewLocalizedStrategyFromJSONBytes(ruleBytes) 103 | assert.NotNil(t, ss) 104 | assert.Nil(t, err) 105 | } 106 | 107 | func TestNewLocalizedStrategyFromInvalidJSONBytes(t *testing.T) { 108 | ruleBytes := []byte(`{ 109 | "version": 1, 110 | "default": { 111 | "fixed_target": 1, 112 | "rate": 113 | }, 114 | "rules": [ 115 | ] 116 | }`) 117 | ss, err := NewLocalizedStrategyFromJSONBytes(ruleBytes) 118 | assert.Nil(t, ss) 119 | assert.NotNil(t, err) 120 | } 121 | 122 | // Benchmarks 123 | func BenchmarkNewLocalizedStrategyFromJSONBytes(b *testing.B) { 124 | ruleBytes := []byte(`{ 125 | "version": 1, 126 | "default": { 127 | "fixed_target": 1, 128 | "rate": 129 | }, 130 | "rules": [ 131 | ] 132 | }`) 133 | for i := 0; i < b.N; i++ { 134 | _, err := NewLocalizedStrategyFromJSONBytes(ruleBytes) 135 | if err != nil { 136 | return 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /strategy/sampling/testdata/rule-v1-contains-host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "default": { 4 | "fixed_target": 1, 5 | "rate": 0.05 6 | }, 7 | "rules": [ 8 | { 9 | "description": "Example path-based rule below. Rules are evaluated in id-order, the default rule will be used if none match the incoming request. This is a rule for the checkout page.", 10 | "id": "1", 11 | "host": "*", 12 | "http_method": "*", 13 | "url_path": "/checkout", 14 | "fixed_target": 10, 15 | "rate": 0.05 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /strategy/sampling/testdata/rule-v1-invalid.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "default": { 4 | "fixed_target": 1, 5 | "rate": 6 | }, 7 | "rules": [] 8 | } 9 | -------------------------------------------------------------------------------- /strategy/sampling/testdata/rule-v1-sampling.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "default": { 4 | "fixed_target": 1, 5 | "rate": 0.05 6 | }, 7 | "rules": [ 8 | { 9 | "description": "Example path-based rule below. Rules are evaluated in id-order, the default rule will be used if none match the incoming request. This is a rule for the checkout page.", 10 | "id": "1", 11 | "service_name": "*", 12 | "http_method": "*", 13 | "url_path": "/checkout", 14 | "fixed_target": 10, 15 | "rate": 0.05 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /strategy/sampling/testdata/rule-v2-contains-service-name.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "default": { 4 | "fixed_target": 1, 5 | "rate": 0.05 6 | }, 7 | "rules": [ 8 | { 9 | "description": "Example path-based rule below. Rules are evaluated in id-order, the default rule will be used if none match the incoming request. This is a rule for the checkout page.", 10 | "id": "1", 11 | "service_name": "*", 12 | "http_method": "*", 13 | "url_path": "/checkout", 14 | "fixed_target": 10, 15 | "rate": 0.05 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /strategy/sampling/testdata/rule-v2-sampling.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "default": { 4 | "fixed_target": 1, 5 | "rate": 0.05 6 | }, 7 | "rules": [ 8 | { 9 | "description": "Example path-based rule below. Rules are evaluated in id-order, the default rule will be used if none match the incoming request. This is a rule for the checkout page.", 10 | "id": "1", 11 | "host": "*", 12 | "http_method": "*", 13 | "url_path": "/checkout", 14 | "fixed_target": 10, 15 | "rate": 0.05 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /terraform/eb.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "3.5.0" 6 | } 7 | } 8 | } 9 | 10 | provider "aws" { 11 | profile = "default" 12 | region = var.region 13 | } 14 | 15 | resource "aws_s3_bucket_public_access_block" "bucket_access" { 16 | bucket = aws_s3_bucket.eb_app_bucket.id 17 | 18 | restrict_public_buckets = true 19 | } 20 | 21 | resource "aws_s3_bucket" "eb_app_bucket" { 22 | bucket = "${var.resource_prefix}.eb.app.applicationversion" 23 | 24 | versioning { 25 | enabled = true 26 | } 27 | 28 | server_side_encryption_configuration { 29 | rule { 30 | apply_server_side_encryption_by_default { 31 | sse_algorithm = "AES256" 32 | } 33 | } 34 | } 35 | } 36 | 37 | resource "aws_s3_bucket_object" "eb_app_package" { 38 | bucket = aws_s3_bucket.eb_app_bucket.id 39 | key = var.bucket_key 40 | source = var.source_path 41 | } 42 | 43 | resource "aws_elastic_beanstalk_application" "eb_app" { 44 | name = "${var.resource_prefix}-EB-App" 45 | description = "Deployment of EB App for integration testing" 46 | } 47 | 48 | resource "aws_elastic_beanstalk_application_version" "eb_app_version" { 49 | name = "${var.resource_prefix}-EB-App-1" 50 | application = aws_elastic_beanstalk_application.eb_app.name 51 | bucket = aws_s3_bucket.eb_app_bucket.id 52 | key = aws_s3_bucket_object.eb_app_package.id 53 | } 54 | 55 | resource "aws_elastic_beanstalk_environment" "eb_env" { 56 | name = "${var.resource_prefix}-EB-App-Env" 57 | application = aws_elastic_beanstalk_application.eb_app.name 58 | solution_stack_name = "64bit Amazon Linux 2 v3.11.6 running Go 1" 59 | tier = "WebServer" 60 | version_label = aws_elastic_beanstalk_application_version.eb_app_version.name 61 | cname_prefix = "${var.resource_prefix}-Eb-app-env" 62 | 63 | setting { 64 | namespace = "aws:autoscaling:launchconfiguration" 65 | name = "IamInstanceProfile" 66 | value = "aws-elasticbeanstalk-ec2-role" 67 | } 68 | 69 | setting { 70 | namespace = "aws:elasticbeanstalk:xray" 71 | name = "XRayEnabled" 72 | value = "true" 73 | } 74 | 75 | setting { 76 | namespace = "aws:autoscaling:launchconfiguration" 77 | name = "DisableIMDSv1" 78 | value = "true" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /terraform/fixtures.us-west-2.tfvars: -------------------------------------------------------------------------------- 1 | region = "us-west-2" 2 | 3 | bucket_key = "beanstalk/deploy.zip" 4 | 5 | source_path = "deploy.zip" 6 | -------------------------------------------------------------------------------- /terraform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | type = string 3 | description = "AWS region for deployment of resources" 4 | } 5 | 6 | variable "bucket_key" { 7 | type = string 8 | description = "AWS s3 object key" 9 | } 10 | 11 | variable "source_path" { 12 | type = string 13 | description = "local source zip path to upload on AWS s3 bucket" 14 | } 15 | 16 | variable "resource_prefix" {} 17 | 18 | -------------------------------------------------------------------------------- /utils/awserr/error.go: -------------------------------------------------------------------------------- 1 | // Copied from: 2 | // https://github.com/aws/aws-sdk-go/blob/v1.55.6/aws/awserr/error.go 3 | 4 | // Package awserr represents API error interface accessors for the SDK. 5 | package awserr 6 | 7 | // An Error wraps lower level errors with code, message and an original error. 8 | // The underlying concrete error type may also satisfy other interfaces which 9 | // can be to used to obtain more specific information about the error. 10 | // 11 | // Calling Error() or String() will always include the full information about 12 | // an error based on its underlying type. 13 | // 14 | // Example: 15 | // 16 | // output, err := s3manage.Upload(svc, input, opts) 17 | // if err != nil { 18 | // if awsErr, ok := err.(awserr.Error); ok { 19 | // // Get error details 20 | // log.Println("Error:", awsErr.Code(), awsErr.Message()) 21 | // 22 | // // Prints out full error message, including original error if there was one. 23 | // log.Println("Error:", awsErr.Error()) 24 | // 25 | // // Get original error 26 | // if origErr := awsErr.OrigErr(); origErr != nil { 27 | // // operate on original error. 28 | // } 29 | // } else { 30 | // fmt.Println(err.Error()) 31 | // } 32 | // } 33 | type Error interface { 34 | // Satisfy the generic error interface. 35 | error 36 | 37 | // Returns the short phrase depicting the classification of the error. 38 | Code() string 39 | 40 | // Returns the error details message. 41 | Message() string 42 | 43 | // Returns the original error if one was set. Nil is returned if not set. 44 | OrigErr() error 45 | } 46 | 47 | // BatchError is a batch of errors which also wraps lower level errors with 48 | // code, message, and original errors. Calling Error() will include all errors 49 | // that occurred in the batch. 50 | // 51 | // Deprecated: Replaced with BatchedErrors. Only defined for backwards 52 | // compatibility. 53 | type BatchError interface { 54 | // Satisfy the generic error interface. 55 | error 56 | 57 | // Returns the short phrase depicting the classification of the error. 58 | Code() string 59 | 60 | // Returns the error details message. 61 | Message() string 62 | 63 | // Returns the original error if one was set. Nil is returned if not set. 64 | OrigErrs() []error 65 | } 66 | 67 | // BatchedErrors is a batch of errors which also wraps lower level errors with 68 | // code, message, and original errors. Calling Error() will include all errors 69 | // that occurred in the batch. 70 | // 71 | // Replaces BatchError 72 | type BatchedErrors interface { 73 | // Satisfy the base Error interface. 74 | Error 75 | 76 | // Returns the original error if one was set. Nil is returned if not set. 77 | OrigErrs() []error 78 | } 79 | 80 | // New returns an Error object described by the code, message, and origErr. 81 | // 82 | // If origErr satisfies the Error interface it will not be wrapped within a new 83 | // Error object and will instead be returned. 84 | func New(code, message string, origErr error) Error { 85 | var errs []error 86 | if origErr != nil { 87 | errs = append(errs, origErr) 88 | } 89 | return newBaseError(code, message, errs) 90 | } 91 | 92 | // NewBatchError returns an BatchedErrors with a collection of errors as an 93 | // array of errors. 94 | func NewBatchError(code, message string, errs []error) BatchedErrors { 95 | return newBaseError(code, message, errs) 96 | } 97 | 98 | // A RequestFailure is an interface to extract request failure information from 99 | // an Error such as the request ID of the failed request returned by a service. 100 | // RequestFailures may not always have a requestID value if the request failed 101 | // prior to reaching the service such as a connection error. 102 | // 103 | // Example: 104 | // 105 | // output, err := s3manage.Upload(svc, input, opts) 106 | // if err != nil { 107 | // if reqerr, ok := err.(RequestFailure); ok { 108 | // log.Println("Request failed", reqerr.Code(), reqerr.Message(), reqerr.RequestID()) 109 | // } else { 110 | // log.Println("Error:", err.Error()) 111 | // } 112 | // } 113 | // 114 | // Combined with awserr.Error: 115 | // 116 | // output, err := s3manage.Upload(svc, input, opts) 117 | // if err != nil { 118 | // if awsErr, ok := err.(awserr.Error); ok { 119 | // // Generic AWS Error with Code, Message, and original error (if any) 120 | // fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr()) 121 | // 122 | // if reqErr, ok := err.(awserr.RequestFailure); ok { 123 | // // A service error occurred 124 | // fmt.Println(reqErr.StatusCode(), reqErr.RequestID()) 125 | // } 126 | // } else { 127 | // fmt.Println(err.Error()) 128 | // } 129 | // } 130 | type RequestFailure interface { 131 | Error 132 | 133 | // The status code of the HTTP response. 134 | StatusCode() int 135 | 136 | // The request ID returned by the service for a request failure. This will 137 | // be empty if no request ID is available such as the request failed due 138 | // to a connection error. 139 | RequestID() string 140 | } 141 | 142 | // NewRequestFailure returns a wrapped error with additional information for 143 | // request status code, and service requestID. 144 | // 145 | // Should be used to wrap all request which involve service requests. Even if 146 | // the request failed without a service response, but had an HTTP status code 147 | // that may be meaningful. 148 | func NewRequestFailure(err Error, statusCode int, reqID string) RequestFailure { 149 | return newRequestError(err, statusCode, reqID) 150 | } 151 | 152 | // UnmarshalError provides the interface for the SDK failing to unmarshal data. 153 | type UnmarshalError interface { 154 | awsError 155 | Bytes() []byte 156 | } 157 | 158 | // NewUnmarshalError returns an initialized UnmarshalError error wrapper adding 159 | // the bytes that fail to unmarshal to the error. 160 | func NewUnmarshalError(err error, msg string, bytes []byte) UnmarshalError { 161 | return &unmarshalError{ 162 | awsError: New("UnmarshalError", msg, err), 163 | bytes: bytes, 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /utils/clock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package utils 10 | 11 | import ( 12 | "time" 13 | ) 14 | 15 | // Clock provides an interface to implement method for getting current time. 16 | type Clock interface { 17 | Now() time.Time 18 | Increment(int64, int64) time.Time 19 | } 20 | 21 | // DefaultClock is an implementation of Clock interface. 22 | type DefaultClock struct{} 23 | 24 | // Now returns current time. 25 | func (t *DefaultClock) Now() time.Time { 26 | return time.Now() 27 | } 28 | 29 | // This method returns the current time but can be used to provide different implementation 30 | func (t *DefaultClock) Increment(_, _ int64) time.Time { 31 | return time.Now() 32 | } 33 | -------------------------------------------------------------------------------- /utils/mock_clock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package utils 10 | 11 | import ( 12 | "sync/atomic" 13 | "time" 14 | ) 15 | 16 | // MockClock is a struct to record current time. 17 | type MockClock struct { 18 | NowTime int64 19 | NowNanos int64 20 | } 21 | 22 | // Now function returns NowTime value. 23 | func (c *MockClock) Now() time.Time { 24 | return time.Unix(c.NowTime, c.NowNanos) 25 | } 26 | 27 | // Increment is a method to increase current time. 28 | func (c *MockClock) Increment(s int64, ns int64) time.Time { 29 | sec := atomic.AddInt64(&c.NowTime, s) 30 | nSec := atomic.AddInt64(&c.NowNanos, ns) 31 | 32 | return time.Unix(sec, nSec) 33 | } 34 | -------------------------------------------------------------------------------- /utils/mock_rand.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package utils 10 | 11 | // MockRand is an implementation of Rand interface. 12 | type MockRand struct { 13 | F64 float64 14 | Int int 15 | Int64 int64 16 | } 17 | 18 | // Float64 returns value of F64. 19 | func (r *MockRand) Float64() float64 { 20 | return r.F64 21 | } 22 | 23 | // Intn returns value of Int. 24 | func (r *MockRand) Intn(n int) int { 25 | return r.Int 26 | } 27 | 28 | // Int63n returns value of Int64. 29 | func (r *MockRand) Int63n(n int64) int64 { 30 | return r.Int64 31 | } 32 | -------------------------------------------------------------------------------- /utils/rand.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package utils 10 | 11 | import ( 12 | crand "crypto/rand" 13 | "encoding/binary" 14 | "math/rand" 15 | "sync" 16 | "time" 17 | ) 18 | 19 | // check lockedSource implements rand.Source 20 | var _ rand.Source = (*lockedSource)(nil) 21 | var _ rand.Source64 = (*lockedSource64)(nil) 22 | 23 | type lockedSource struct { 24 | mu sync.Mutex 25 | src rand.Source 26 | } 27 | 28 | func (src *lockedSource) Int63() int64 { 29 | src.mu.Lock() 30 | defer src.mu.Unlock() 31 | return src.src.Int63() 32 | } 33 | 34 | func (src *lockedSource) Seed(seed int64) { 35 | src.mu.Lock() 36 | defer src.mu.Unlock() 37 | src.src.Seed(seed) 38 | } 39 | 40 | type lockedSource64 struct { 41 | mu sync.Mutex 42 | src rand.Source64 43 | } 44 | 45 | func (src *lockedSource64) Int63() int64 { 46 | src.mu.Lock() 47 | defer src.mu.Unlock() 48 | return src.src.Int63() 49 | } 50 | 51 | func (src *lockedSource64) Uint64() uint64 { 52 | src.mu.Lock() 53 | defer src.mu.Unlock() 54 | return src.src.Uint64() 55 | } 56 | 57 | func (src *lockedSource64) Seed(seed int64) { 58 | src.mu.Lock() 59 | defer src.mu.Unlock() 60 | src.src.Seed(seed) 61 | } 62 | 63 | func newSeed() int64 { 64 | var seed int64 65 | if err := binary.Read(crand.Reader, binary.BigEndian, &seed); err != nil { 66 | // fallback to timestamp 67 | seed = time.Now().UnixNano() 68 | } 69 | return seed 70 | } 71 | 72 | func newGlobalRand() *rand.Rand { 73 | src := rand.NewSource(newSeed()) 74 | if src64, ok := src.(rand.Source64); ok { 75 | return rand.New(&lockedSource64{src: src64}) 76 | } 77 | return rand.New(&lockedSource{src: src}) 78 | } 79 | 80 | // Rand is an interface for a set of methods that return random value. 81 | type Rand interface { 82 | Int63n(n int64) int64 83 | Intn(n int) int 84 | Float64() float64 85 | } 86 | 87 | // DefaultRand is an implementation of Rand interface. 88 | // It is safe for concurrent use by multiple goroutines. 89 | type DefaultRand struct{} 90 | 91 | var globalRand = newGlobalRand() 92 | 93 | // Int63n returns, as an int64, a non-negative pseudo-random number in [0,n) 94 | // from the default Source. 95 | func (r *DefaultRand) Int63n(n int64) int64 { 96 | return globalRand.Int63n(n) 97 | } 98 | 99 | // Intn returns, as an int, a non-negative pseudo-random number in [0,n) 100 | // from the default Source. 101 | func (r *DefaultRand) Intn(n int) int { 102 | return globalRand.Intn(n) 103 | } 104 | 105 | // Float64 returns, as a float64, a pseudo-random number in [0.0,1.0) 106 | // from the default Source. 107 | func (r *DefaultRand) Float64() float64 { 108 | return globalRand.Float64() 109 | } 110 | -------------------------------------------------------------------------------- /utils/timer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package utils 10 | 11 | import ( 12 | "time" 13 | ) 14 | 15 | // Timer is the same as time.Timer except that it has jitters. 16 | // A Timer must be created with NewTimer. 17 | type Timer struct { 18 | t *time.Timer 19 | d time.Duration 20 | jitter time.Duration 21 | } 22 | 23 | // NewTimer creates a new Timer that will send the current time on its channel. 24 | func NewTimer(d, jitter time.Duration) *Timer { 25 | t := time.NewTimer(d - time.Duration(globalRand.Int63n(int64(jitter)))) 26 | 27 | jitteredTimer := Timer{ 28 | t: t, 29 | d: d, 30 | jitter: jitter, 31 | } 32 | 33 | return &jitteredTimer 34 | } 35 | 36 | // C is channel. 37 | func (j *Timer) C() <-chan time.Time { 38 | return j.t.C 39 | } 40 | 41 | // Reset resets the timer. 42 | // Reset should be invoked only on stopped or expired timers with drained channels. 43 | func (j *Timer) Reset() { 44 | j.t.Reset(j.d - time.Duration(globalRand.Int63n(int64(j.jitter)))) 45 | } 46 | -------------------------------------------------------------------------------- /xray/capture.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package xray 10 | 11 | import ( 12 | "context" 13 | "fmt" 14 | ) 15 | 16 | // Capture traces the provided synchronous function by 17 | // beginning and closing a subsegment around its execution. 18 | func Capture(ctx context.Context, name string, fn func(context.Context) error) (err error) { 19 | c, seg := BeginSubsegment(ctx, name) 20 | 21 | defer func() { 22 | if seg != nil { 23 | seg.Close(err) 24 | } else { 25 | cfg := GetRecorder(ctx) 26 | failedMessage := fmt.Sprintf("failed to end subsegment: subsegment '%v' cannot be found.", name) 27 | if cfg != nil && cfg.ContextMissingStrategy != nil { 28 | cfg.ContextMissingStrategy.ContextMissing(failedMessage) 29 | } else { 30 | globalCfg.ContextMissingStrategy().ContextMissing(failedMessage) 31 | } 32 | } 33 | }() 34 | 35 | defer func() { 36 | if p := recover(); p != nil { 37 | err = seg.ParentSegment.GetConfiguration().ExceptionFormattingStrategy.Panicf("%v", p) 38 | panic(p) 39 | } 40 | }() 41 | 42 | if c == nil && seg == nil { 43 | err = fn(ctx) 44 | } else { 45 | err = fn(c) 46 | } 47 | 48 | return err 49 | } 50 | 51 | // CaptureAsync traces an arbitrary code segment within a goroutine. 52 | // Use CaptureAsync instead of manually calling Capture within a goroutine 53 | // to ensure the segment is flushed properly. 54 | func CaptureAsync(ctx context.Context, name string, fn func(context.Context) error) { 55 | started := make(chan struct{}) 56 | go Capture(ctx, name, func(ctx context.Context) error { 57 | close(started) 58 | return fn(ctx) 59 | }) 60 | <-started 61 | } 62 | -------------------------------------------------------------------------------- /xray/capture_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package xray 10 | 11 | import ( 12 | "context" 13 | "encoding/json" 14 | "errors" 15 | "sync" 16 | "testing" 17 | 18 | "github.com/aws/aws-xray-sdk-go/v2/strategy/exception" 19 | "github.com/stretchr/testify/assert" 20 | ) 21 | 22 | func TestSimpleCapture(t *testing.T) { 23 | ctx, td := NewTestDaemon() 24 | defer td.Close() 25 | 26 | ctx, root := BeginSegment(ctx, "Test") 27 | err := Capture(ctx, "TestService", func(context.Context) error { 28 | root.Close(nil) 29 | return nil 30 | }) 31 | assert.NoError(t, err) 32 | 33 | seg, err := td.Recv() 34 | if !assert.NoError(t, err) { 35 | return 36 | } 37 | assert.Equal(t, "Test", seg.Name) 38 | assert.Equal(t, root.TraceID, seg.TraceID) 39 | assert.Equal(t, root.ID, seg.ID) 40 | assert.Equal(t, root.StartTime, seg.StartTime) 41 | assert.Equal(t, root.EndTime, seg.EndTime) 42 | assert.NotNil(t, seg.Subsegments) 43 | var subseg *Segment 44 | if assert.NoError(t, json.Unmarshal(seg.Subsegments[0], &subseg)) { 45 | assert.Equal(t, "TestService", subseg.Name) 46 | } 47 | } 48 | 49 | func TestErrorCapture(t *testing.T) { 50 | ctx, td := NewTestDaemon() 51 | defer td.Close() 52 | 53 | ctx, root := BeginSegment(ctx, "Test") 54 | defaultStrategy, err := exception.NewDefaultFormattingStrategy() 55 | if !assert.NoError(t, err) { 56 | return 57 | } 58 | captureErr := Capture(ctx, "ErrorService", func(context.Context) error { 59 | defer root.Close(nil) 60 | return defaultStrategy.Error("MyError") 61 | }) 62 | if !assert.Error(t, captureErr) { 63 | return 64 | } 65 | 66 | seg, err := td.Recv() 67 | if !assert.NoError(t, err) { 68 | return 69 | } 70 | var subseg *Segment 71 | if !assert.NoError(t, json.Unmarshal(seg.Subsegments[0], &subseg)) { 72 | return 73 | } 74 | assert.Equal(t, captureErr.Error(), subseg.Cause.Exceptions[0].Message) 75 | assert.Equal(t, true, subseg.Fault) 76 | assert.Equal(t, "error", subseg.Cause.Exceptions[0].Type) 77 | assert.Equal(t, "TestErrorCapture.func1", subseg.Cause.Exceptions[0].Stack[0].Label) 78 | assert.Equal(t, "Capture", subseg.Cause.Exceptions[0].Stack[1].Label) 79 | } 80 | 81 | func TestPanicCapture(t *testing.T) { 82 | ctx, td := NewTestDaemon() 83 | defer td.Close() 84 | 85 | ctx, root := BeginSegment(ctx, "Test") 86 | var captureErr error 87 | func() { 88 | defer func() { 89 | if p := recover(); p != nil { 90 | captureErr = errors.New(p.(string)) 91 | } 92 | root.Close(captureErr) 93 | }() 94 | _ = Capture(ctx, "PanicService", func(context.Context) error { 95 | panic("MyPanic") 96 | }) 97 | }() 98 | 99 | seg, err := td.Recv() 100 | if !assert.NoError(t, err) { 101 | return 102 | } 103 | var subseg *Segment 104 | if !assert.NoError(t, json.Unmarshal(seg.Subsegments[0], &subseg)) { 105 | return 106 | } 107 | assert.Equal(t, captureErr.Error(), subseg.Cause.Exceptions[0].Message) 108 | assert.Equal(t, "panic", subseg.Cause.Exceptions[0].Type) 109 | assert.Equal(t, "TestPanicCapture.func1.2", subseg.Cause.Exceptions[0].Stack[0].Label) 110 | assert.Equal(t, "Capture", subseg.Cause.Exceptions[0].Stack[1].Label) 111 | assert.Equal(t, "TestPanicCapture.func1", subseg.Cause.Exceptions[0].Stack[2].Label) 112 | assert.Equal(t, "TestPanicCapture", subseg.Cause.Exceptions[0].Stack[3].Label) 113 | } 114 | 115 | func TestNoSegmentCapture(t *testing.T) { 116 | ctx, _ := NewTestDaemon() 117 | _, seg := BeginSubsegment(ctx, "Name") 118 | seg.Close(nil) 119 | } 120 | 121 | func TestCaptureAsync(t *testing.T) { 122 | ctx, td := NewTestDaemon() 123 | defer td.Close() 124 | 125 | var wg sync.WaitGroup 126 | wg.Add(1) 127 | ctx, root := BeginSegment(ctx, "Test") 128 | CaptureAsync(ctx, "TestService", func(context.Context) error { 129 | defer wg.Done() 130 | root.Close(nil) 131 | return nil 132 | }) 133 | 134 | seg, err := td.Recv() 135 | if !assert.NoError(t, err) { 136 | return 137 | } 138 | 139 | wg.Wait() 140 | assert.Equal(t, "Test", seg.Name) 141 | assert.Equal(t, root.TraceID, seg.TraceID) 142 | assert.Equal(t, root.ID, seg.ID) 143 | assert.Equal(t, root.StartTime, seg.StartTime) 144 | assert.Equal(t, root.EndTime, seg.EndTime) 145 | assert.NotNil(t, seg.Subsegments) 146 | var subseg *Segment 147 | if assert.NoError(t, json.Unmarshal(seg.Subsegments[0], &subseg)) { 148 | assert.Equal(t, "TestService", subseg.Name) 149 | } 150 | } 151 | 152 | // Benchmarks 153 | func BenchmarkCapture(b *testing.B) { 154 | ctx, seg := BeginSegment(context.Background(), "TestCaptureSeg") 155 | for i := 0; i < b.N; i++ { 156 | Capture(ctx, "TestCaptureSubSeg", func(ctx context.Context) error { 157 | return nil 158 | }) 159 | } 160 | seg.Close(nil) 161 | } 162 | -------------------------------------------------------------------------------- /xray/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package xray 10 | 11 | import ( 12 | "context" 13 | "net/http" 14 | "net/http/httptrace" 15 | "net/url" 16 | "strconv" 17 | "strings" 18 | 19 | "github.com/aws/aws-xray-sdk-go/v2/internal/logger" 20 | ) 21 | 22 | // TraceIDHeaderKey is the HTTP header name used for tracing. 23 | const TraceIDHeaderKey = "x-amzn-trace-id" 24 | 25 | const emptyHostRename = "empty_host_error" 26 | 27 | // Client creates a shallow copy of the provided http client, 28 | // defaulting to http.DefaultClient, with roundtripper wrapped 29 | // with xray.RoundTripper. 30 | func Client(c *http.Client) *http.Client { 31 | if c == nil { 32 | c = http.DefaultClient 33 | } 34 | transport := c.Transport 35 | if transport == nil { 36 | transport = http.DefaultTransport 37 | } 38 | return &http.Client{ 39 | Transport: RoundTripper(transport), 40 | CheckRedirect: c.CheckRedirect, 41 | Jar: c.Jar, 42 | Timeout: c.Timeout, 43 | } 44 | } 45 | 46 | // RoundTripper wraps the provided http roundtripper with xray.Capture, 47 | // sets HTTP-specific xray fields, and adds the trace header to the outbound request. 48 | func RoundTripper(rt http.RoundTripper) http.RoundTripper { 49 | return &roundtripper{rt} 50 | } 51 | 52 | type roundtripper struct { 53 | Base http.RoundTripper 54 | } 55 | 56 | // RoundTrip wraps a single HTTP transaction and add corresponding information into a subsegment. 57 | func (rt *roundtripper) RoundTrip(r *http.Request) (*http.Response, error) { 58 | var isEmptyHost bool 59 | var resp *http.Response 60 | host := r.Host 61 | if host == "" { 62 | if h := r.URL.Host; h != "" { 63 | host = h 64 | } else { 65 | host = emptyHostRename 66 | isEmptyHost = true 67 | } 68 | } 69 | 70 | err := Capture(r.Context(), host, func(ctx context.Context) error { 71 | var err error 72 | seg := GetSegment(ctx) 73 | if seg == nil { 74 | resp, err = rt.Base.RoundTrip(r) 75 | logger.Warnf("failed to record HTTP transaction: segment cannot be found.") 76 | return err 77 | } 78 | 79 | ct, e := NewClientTrace(ctx) 80 | if e != nil { 81 | return e 82 | } 83 | r = r.WithContext(httptrace.WithClientTrace(ctx, ct.httpTrace)) 84 | 85 | seg.Lock() 86 | 87 | if isEmptyHost { 88 | seg.Namespace = "" 89 | } else { 90 | seg.Namespace = "remote" 91 | } 92 | 93 | seg.GetHTTP().GetRequest().Method = r.Method 94 | seg.GetHTTP().GetRequest().URL = stripURL(*r.URL) 95 | 96 | r.Header.Set(TraceIDHeaderKey, seg.DownstreamHeader().String()) 97 | seg.Unlock() 98 | 99 | resp, err = rt.Base.RoundTrip(r) 100 | 101 | if resp != nil { 102 | seg.Lock() 103 | seg.GetHTTP().GetResponse().Status = resp.StatusCode 104 | seg.GetHTTP().GetResponse().ContentLength, _ = strconv.Atoi(resp.Header.Get("Content-Length")) 105 | 106 | if resp.StatusCode >= 400 && resp.StatusCode < 500 { 107 | seg.Error = true 108 | } 109 | if resp.StatusCode == 429 { 110 | seg.Throttle = true 111 | } 112 | if resp.StatusCode >= 500 && resp.StatusCode < 600 { 113 | seg.Fault = true 114 | } 115 | seg.Unlock() 116 | } 117 | if err != nil { 118 | ct.subsegments.GotConn(nil, err) 119 | } 120 | 121 | return err 122 | }) 123 | return resp, err 124 | } 125 | 126 | func stripURL(u url.URL) string { 127 | u.RawQuery = "" 128 | _, passSet := u.User.Password() 129 | if passSet { 130 | return strings.Replace(u.String(), u.User.String()+"@", u.User.Username()+":***@", 1) 131 | } 132 | return u.String() 133 | } 134 | -------------------------------------------------------------------------------- /xray/context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package xray 10 | 11 | import ( 12 | "context" 13 | "errors" 14 | ) 15 | 16 | // ContextKeytype defines integer to be type of ContextKey. 17 | type ContextKeytype int 18 | 19 | // ContextKey returns a pointer to a newly allocated zero value of ContextKeytype. 20 | var ContextKey = new(ContextKeytype) 21 | 22 | // ErrRetrieveSegment happens when a segment cannot be retrieved 23 | var ErrRetrieveSegment = errors.New("unable to retrieve segment") 24 | 25 | // RecorderContextKey records the key for Config value. 26 | type RecorderContextKey struct{} 27 | 28 | const ( 29 | // fasthttpContextKey records the key for Segment value 30 | // this was necessary because fasthttp only accepts strings as keys in contexts 31 | fasthttpContextKey = "xray-ck" 32 | 33 | // fasthttpContextConfigKey records the key for Config value 34 | fasthttpContextConfigKey = "xray-rck" 35 | ) 36 | 37 | // GetRecorder returns a pointer to the config struct provided 38 | // in ctx, or nil if no config is set. 39 | func GetRecorder(ctx context.Context) *Config { 40 | if r, ok := ctx.Value(RecorderContextKey{}).(*Config); ok { 41 | return r 42 | } 43 | 44 | if r, ok := ctx.Value(fasthttpContextConfigKey).(*Config); ok { 45 | return r 46 | } 47 | return nil 48 | } 49 | 50 | // GetSegment returns a pointer to the segment or subsegment provided 51 | // in ctx, or nil if no segment or subsegment is found. 52 | func GetSegment(ctx context.Context) *Segment { 53 | if seg, ok := ctx.Value(ContextKey).(*Segment); ok { 54 | return seg 55 | } 56 | 57 | if seg, ok := ctx.Value(fasthttpContextKey).(*Segment); ok { 58 | return seg 59 | } 60 | 61 | return nil 62 | } 63 | 64 | // TraceID returns the canonical ID of the cross-service trace from the 65 | // given segment in ctx. The value can be used in X-Ray's UI to uniquely 66 | // identify the code paths executed. If no segment is provided in ctx, 67 | // an empty string is returned. 68 | func TraceID(ctx context.Context) string { 69 | 70 | if seg := GetSegment(ctx); seg != nil { 71 | return seg.TraceID 72 | } 73 | 74 | return "" 75 | } 76 | 77 | // RequestWasTraced returns true if the context contains an X-Ray segment 78 | // that was created from an HTTP request that contained a trace header. 79 | // This is useful to ensure that a service is only called from X-Ray traced 80 | // services. 81 | func RequestWasTraced(ctx context.Context) bool { 82 | for seg := GetSegment(ctx); seg != nil; seg = seg.parent { 83 | if seg.RequestWasTraced { 84 | return true 85 | } 86 | } 87 | return false 88 | } 89 | 90 | // DetachContext returns a new context with the existing segment. 91 | // This is useful for creating background tasks which won't be cancelled 92 | // when a request completes. 93 | func DetachContext(ctx context.Context) context.Context { 94 | return context.WithValue(context.Background(), ContextKey, GetSegment(ctx)) 95 | } 96 | 97 | // AddAnnotation adds an annotation to the provided segment or subsegment in ctx. 98 | func AddAnnotation(ctx context.Context, key string, value interface{}) error { 99 | if seg := GetSegment(ctx); seg != nil { 100 | return seg.AddAnnotation(key, value) 101 | } 102 | return ErrRetrieveSegment 103 | } 104 | 105 | // AddMetadata adds a metadata to the provided segment or subsegment in ctx. 106 | func AddMetadata(ctx context.Context, key string, value interface{}) error { 107 | if seg := GetSegment(ctx); seg != nil { 108 | return seg.AddMetadata(key, value) 109 | } 110 | return ErrRetrieveSegment 111 | } 112 | 113 | // AddMetadataToNamespace adds a namespace to the provided segment's or subsegment's metadata in ctx. 114 | func AddMetadataToNamespace(ctx context.Context, namespace string, key string, value interface{}) error { 115 | if seg := GetSegment(ctx); seg != nil { 116 | return seg.AddMetadataToNamespace(namespace, key, value) 117 | } 118 | return ErrRetrieveSegment 119 | } 120 | 121 | // AddError adds an error to the provided segment or subsegment in ctx. 122 | func AddError(ctx context.Context, err error) error { 123 | if seg := GetSegment(ctx); seg != nil { 124 | return seg.AddError(err) 125 | } 126 | return ErrRetrieveSegment 127 | } 128 | -------------------------------------------------------------------------------- /xray/context_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package xray 10 | 11 | import ( 12 | "context" 13 | "errors" 14 | "testing" 15 | 16 | "github.com/aws/aws-xray-sdk-go/v2/strategy/exception" 17 | "github.com/stretchr/testify/assert" 18 | ) 19 | 20 | func TestTraceID(t *testing.T) { 21 | ctx, td := NewTestDaemon() 22 | defer td.Close() 23 | 24 | ctx, seg := BeginSegment(ctx, "test") 25 | defer seg.Close(nil) 26 | traceID := TraceID(ctx) 27 | assert.Equal(t, seg.TraceID, traceID) 28 | } 29 | 30 | func TestEmptyTraceID(t *testing.T) { 31 | traceID := TraceID(context.Background()) 32 | assert.Empty(t, traceID) 33 | } 34 | 35 | func TestRequestWasNotTraced(t *testing.T) { 36 | ctx, td := NewTestDaemon() 37 | defer td.Close() 38 | 39 | ctx, seg := BeginSegment(ctx, "test") 40 | defer seg.Close(nil) 41 | assert.Equal(t, seg.RequestWasTraced, RequestWasTraced(ctx)) 42 | } 43 | 44 | func TestDetachContext(t *testing.T) { 45 | ctx, td := NewTestDaemon() 46 | defer td.Close() 47 | 48 | ctx, cancel := context.WithCancel(ctx) 49 | defer cancel() 50 | 51 | ctx1, seg := BeginSegment(ctx, "test") 52 | defer seg.Close(nil) 53 | ctx2 := DetachContext(ctx1) 54 | cancel() 55 | 56 | assert.Equal(t, seg, GetSegment(ctx2)) 57 | select { 58 | case <-ctx2.Done(): 59 | assert.Error(t, ctx2.Err()) 60 | default: 61 | // ctx1 is canceled, but ctx2 is not. 62 | } 63 | } 64 | 65 | func TestValidAnnotations(t *testing.T) { 66 | ctx, td := NewTestDaemon() 67 | defer td.Close() 68 | 69 | ctx, root := BeginSegment(ctx, "Test") 70 | 71 | var err exception.MultiError 72 | if e := AddAnnotation(ctx, "string", "str"); e != nil { 73 | err = append(err, e) 74 | } 75 | if e := AddAnnotation(ctx, "int", 1); e != nil { 76 | err = append(err, e) 77 | } 78 | if e := AddAnnotation(ctx, "bool", true); e != nil { 79 | err = append(err, e) 80 | } 81 | if e := AddAnnotation(ctx, "float", 1.1); e != nil { 82 | err = append(err, e) 83 | } 84 | root.Close(err) 85 | 86 | seg, e := td.Recv() 87 | if !assert.NoError(t, e) { 88 | return 89 | } 90 | 91 | assert.Equal(t, "str", seg.Annotations["string"]) 92 | assert.Equal(t, 1.0, seg.Annotations["int"]) //json encoder turns this into a float64 93 | assert.Equal(t, 1.1, seg.Annotations["float"]) 94 | assert.Equal(t, true, seg.Annotations["bool"]) 95 | } 96 | 97 | func TestInvalidAnnotations(t *testing.T) { 98 | ctx, td := NewTestDaemon() 99 | defer td.Close() 100 | 101 | ctx, root := BeginSegment(ctx, "Test") 102 | type MyObject struct{} 103 | 104 | err := AddAnnotation(ctx, "Object", &MyObject{}) 105 | root.Close(err) 106 | assert.Error(t, err) 107 | 108 | seg, err := td.Recv() 109 | if assert.NoError(t, err) { 110 | assert.NotContains(t, seg.Annotations, "Object") 111 | } 112 | } 113 | 114 | func TestSimpleMetadata(t *testing.T) { 115 | ctx, td := NewTestDaemon() 116 | defer td.Close() 117 | 118 | ctx, root := BeginSegment(ctx, "Test") 119 | var err exception.MultiError 120 | if e := AddMetadata(ctx, "string", "str"); e != nil { 121 | err = append(err, e) 122 | } 123 | if e := AddMetadata(ctx, "int", 1); e != nil { 124 | err = append(err, e) 125 | } 126 | if e := AddMetadata(ctx, "bool", true); e != nil { 127 | err = append(err, e) 128 | } 129 | if e := AddMetadata(ctx, "float", 1.1); e != nil { 130 | err = append(err, e) 131 | } 132 | assert.Nil(t, err) 133 | root.Close(err) 134 | 135 | seg, e := td.Recv() 136 | if !assert.NoError(t, e) { 137 | return 138 | } 139 | assert.Equal(t, "str", seg.Metadata["default"]["string"]) 140 | assert.Equal(t, 1.0, seg.Metadata["default"]["int"]) //json encoder turns this into a float64 141 | assert.Equal(t, 1.1, seg.Metadata["default"]["float"]) 142 | assert.Equal(t, true, seg.Metadata["default"]["bool"]) 143 | } 144 | 145 | func TestAddError(t *testing.T) { 146 | ctx, td := NewTestDaemon() 147 | defer td.Close() 148 | 149 | ctx, root := BeginSegment(ctx, "Test") 150 | err := AddError(ctx, errors.New("New Error")) 151 | assert.NoError(t, err) 152 | root.Close(err) 153 | 154 | seg, err := td.Recv() 155 | if !assert.NoError(t, err) { 156 | return 157 | } 158 | assert.Equal(t, "New Error", seg.Cause.Exceptions[0].Message) 159 | assert.Equal(t, "errors.errorString", seg.Cause.Exceptions[0].Type) 160 | } 161 | 162 | // Benchmarks 163 | func BenchmarkGetRecorder(b *testing.B) { 164 | ctx, td := NewTestDaemon() 165 | defer td.Close() 166 | ctx, seg := BeginSegment(ctx, "TestSeg") 167 | for i := 0; i < b.N; i++ { 168 | GetRecorder(ctx) 169 | } 170 | seg.Close(nil) 171 | } 172 | 173 | func BenchmarkGetSegment(b *testing.B) { 174 | ctx, td := NewTestDaemon() 175 | defer td.Close() 176 | ctx, seg := BeginSegment(ctx, "TestSeg") 177 | for i := 0; i < b.N; i++ { 178 | GetSegment(ctx) 179 | } 180 | seg.Close(nil) 181 | } 182 | 183 | func BenchmarkDetachContext(b *testing.B) { 184 | ctx, td := NewTestDaemon() 185 | defer td.Close() 186 | ctx, seg := BeginSegment(ctx, "TestSeg") 187 | for i := 0; i < b.N; i++ { 188 | DetachContext(ctx) 189 | } 190 | seg.Close(nil) 191 | } 192 | 193 | func BenchmarkAddAnnotation(b *testing.B) { 194 | ctx, td := NewTestDaemon() 195 | defer td.Close() 196 | ctx, seg := BeginSegment(ctx, "TestSeg") 197 | for i := 0; i < b.N; i++ { 198 | err := AddAnnotation(ctx, "key", "value") 199 | if err != nil { 200 | return 201 | } 202 | } 203 | seg.Close(nil) 204 | } 205 | 206 | func BenchmarkAddMetadata(b *testing.B) { 207 | ctx, td := NewTestDaemon() 208 | defer td.Close() 209 | ctx, seg := BeginSegment(ctx, "TestSeg") 210 | for i := 0; i < b.N; i++ { 211 | err := AddMetadata(ctx, "key", "value") 212 | if err != nil { 213 | return 214 | } 215 | } 216 | seg.Close(nil) 217 | } 218 | -------------------------------------------------------------------------------- /xray/default_emitter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package xray 10 | 11 | import ( 12 | "encoding/json" 13 | "net" 14 | "runtime/debug" 15 | "sync" 16 | 17 | "github.com/aws/aws-xray-sdk-go/v2/internal/logger" 18 | ) 19 | 20 | // Header is added before sending segments to daemon. 21 | const Header = `{"format": "json", "version": 1}` + "\n" 22 | 23 | // DefaultEmitter provides the naive implementation of emitting trace entities. 24 | type DefaultEmitter struct { 25 | sync.Mutex 26 | conn *net.UDPConn 27 | addr *net.UDPAddr 28 | } 29 | 30 | // NewDefaultEmitter initializes and returns a 31 | // pointer to an instance of DefaultEmitter. 32 | func NewDefaultEmitter(raddr *net.UDPAddr) (*DefaultEmitter, error) { 33 | initLambda() 34 | d := &DefaultEmitter{addr: raddr} 35 | return d, nil 36 | } 37 | 38 | // RefreshEmitterWithAddress dials UDP based on the input UDP address. 39 | func (de *DefaultEmitter) RefreshEmitterWithAddress(raddr *net.UDPAddr) { 40 | de.Lock() 41 | de.refresh(raddr) 42 | de.Unlock() 43 | } 44 | 45 | func (de *DefaultEmitter) refresh(raddr *net.UDPAddr) (err error) { 46 | de.conn, err = net.DialUDP("udp", nil, raddr) 47 | de.addr = raddr 48 | 49 | if err != nil { 50 | logger.Errorf("Error dialing emitter address %v: %s", raddr, err) 51 | return err 52 | } 53 | 54 | logger.Infof("Emitter using address: %v", raddr) 55 | return nil 56 | } 57 | 58 | // Emit segment or subsegment if root segment is sampled. 59 | // seg has a write lock acquired by the caller. 60 | func (de *DefaultEmitter) Emit(seg *Segment) { 61 | defer func() { 62 | if r := recover(); r != nil { 63 | logger.Errorf("Panic emitting segment: %s\n%s", r, string(debug.Stack())) 64 | } 65 | }() 66 | HeaderBytes := []byte(Header) 67 | 68 | if seg == nil || !seg.ParentSegment.Sampled { 69 | return 70 | } 71 | 72 | for _, p := range packSegments(seg, nil) { 73 | logger.Debug(string(p)) 74 | 75 | de.Lock() 76 | 77 | if de.conn == nil { 78 | if err := de.refresh(de.addr); err != nil { 79 | de.Unlock() 80 | return 81 | } 82 | } 83 | 84 | _, err := de.conn.Write(append(HeaderBytes, p...)) 85 | if err != nil { 86 | logger.Error(err) 87 | } 88 | de.Unlock() 89 | } 90 | } 91 | 92 | // seg has a write lock acquired by the caller. 93 | func packSegments(seg *Segment, outSegments [][]byte) [][]byte { 94 | trimSubsegment := func(s *Segment) []byte { 95 | ss := globalCfg.StreamingStrategy() 96 | if seg.ParentSegment.Configuration != nil && seg.ParentSegment.Configuration.StreamingStrategy != nil { 97 | ss = seg.ParentSegment.Configuration.StreamingStrategy 98 | } 99 | for ss.RequiresStreaming(s) { 100 | if len(s.rawSubsegments) == 0 { 101 | break 102 | } 103 | cb := ss.StreamCompletedSubsegments(s) 104 | outSegments = append(outSegments, cb...) 105 | } 106 | b, err := json.Marshal(s) 107 | if err != nil { 108 | logger.Errorf("JSON error while marshalling (Sub)Segment: %v", err) 109 | } 110 | return b 111 | } 112 | 113 | for _, s := range seg.rawSubsegments { 114 | s.Lock() 115 | outSegments = packSegments(s, outSegments) 116 | if b := trimSubsegment(s); b != nil { 117 | seg.Subsegments = append(seg.Subsegments, b) 118 | } 119 | s.Unlock() 120 | } 121 | if seg.isOrphan() { 122 | if b := trimSubsegment(seg); b != nil { 123 | outSegments = append(outSegments, b) 124 | } 125 | } 126 | return outSegments 127 | } 128 | -------------------------------------------------------------------------------- /xray/default_emitter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package xray 10 | 11 | import ( 12 | "encoding/json" 13 | "fmt" 14 | "math/rand" 15 | "net" 16 | "testing" 17 | "time" 18 | 19 | "github.com/stretchr/testify/assert" 20 | ) 21 | 22 | func TestNoNeedStreamingStrategy(t *testing.T) { 23 | seg := &Segment{} 24 | subSeg := &Segment{} 25 | assert.NoError(t, json.Unmarshal([]byte(getTestSegment()), &seg)) 26 | assert.NoError(t, json.Unmarshal([]byte(getTestSegment()), &subSeg)) 27 | subSeg.ParentSegment = seg 28 | subSeg.parent = seg 29 | seg.ParentSegment = seg 30 | seg.Sampled = true 31 | seg.totalSubSegments = 1 32 | seg.rawSubsegments = append(seg.rawSubsegments, subSeg) 33 | assert.Equal(t, 1, len(packSegments(seg, nil))) 34 | } 35 | 36 | func TestStreamingSegmentsOnChildNode(t *testing.T) { 37 | seg := &Segment{} 38 | subSeg := &Segment{} 39 | assert.NoError(t, json.Unmarshal([]byte(getTestSegment()), &seg)) 40 | assert.NoError(t, json.Unmarshal([]byte(getTestSegment()), &subSeg)) 41 | subSeg.parent = seg 42 | seg.ParentSegment = seg 43 | subSeg.ParentSegment = seg.ParentSegment 44 | seg.Sampled = true 45 | seg.totalSubSegments = 22 46 | 47 | for i := 0; i < 22; i++ { 48 | seg.rawSubsegments = append(seg.rawSubsegments, subSeg) 49 | } 50 | 51 | out := packSegments(seg, nil) 52 | s := &Segment{} 53 | json.Unmarshal(out[2], s) 54 | assert.Equal(t, 20, len(s.Subsegments)) 55 | assert.Equal(t, 3, len(out)) 56 | } 57 | 58 | func TestStreamingSegmentsOnGrandchildNode(t *testing.T) { 59 | root := &Segment{} 60 | a := &Segment{} 61 | b := &Segment{} 62 | c := &Segment{} 63 | d := &Segment{} 64 | assert.NoError(t, json.Unmarshal([]byte(getTestSegment()), &root)) 65 | assert.NoError(t, json.Unmarshal([]byte(getTestSegment()), &a)) 66 | assert.NoError(t, json.Unmarshal([]byte(getTestSegment()), &b)) 67 | assert.NoError(t, json.Unmarshal([]byte(getTestSegment()), &c)) 68 | assert.NoError(t, json.Unmarshal([]byte(getTestSegment()), &d)) 69 | 70 | root.ParentSegment = root 71 | root.Sampled = true 72 | a.ParentSegment = root 73 | b.ParentSegment = root 74 | c.ParentSegment = root 75 | d.ParentSegment = root 76 | a.parent = root 77 | b.parent = root 78 | c.parent = a 79 | d.parent = b 80 | root.totalSubSegments = 42 81 | root.rawSubsegments = append(root.rawSubsegments, a) 82 | root.rawSubsegments = append(root.rawSubsegments, b) 83 | 84 | for i := 0; i < 20; i++ { 85 | a.rawSubsegments = append(a.rawSubsegments, c) 86 | } 87 | for i := 0; i < 20; i++ { 88 | b.rawSubsegments = append(b.rawSubsegments, d) 89 | } 90 | assert.Equal(t, 23, len(packSegments(root, nil))) 91 | } 92 | 93 | func TestStreamingSegmentsTreeHasOnlyOneBranch(t *testing.T) { 94 | dss, _ := NewDefaultStreamingStrategyWithMaxSubsegmentCount(1) 95 | Configure(Config{StreamingStrategy: dss}) 96 | segOne := &Segment{} 97 | segTwo := &Segment{} 98 | segThree := &Segment{} 99 | segFour := &Segment{} 100 | assert.NoError(t, json.Unmarshal([]byte(getTestSegment()), &segOne)) 101 | assert.NoError(t, json.Unmarshal([]byte(getTestSegment()), &segTwo)) 102 | assert.NoError(t, json.Unmarshal([]byte(getTestSegment()), &segThree)) 103 | assert.NoError(t, json.Unmarshal([]byte(getTestSegment()), &segFour)) 104 | 105 | segOne.ParentSegment = segOne 106 | segOne.Sampled = true 107 | segTwo.ParentSegment = segOne 108 | segTwo.parent = segOne 109 | segThree.ParentSegment = segOne 110 | segThree.parent = segTwo 111 | segFour.ParentSegment = segOne 112 | segFour.parent = segThree 113 | 114 | segOne.totalSubSegments = 3 115 | segOne.rawSubsegments = append(segOne.rawSubsegments, segTwo) 116 | segTwo.rawSubsegments = append(segTwo.rawSubsegments, segThree) 117 | segThree.rawSubsegments = append(segThree.rawSubsegments, segFour) 118 | 119 | assert.Equal(t, 3, len(packSegments(segOne, nil))) 120 | ResetConfig() 121 | } 122 | 123 | func randomString(strlen int) string { 124 | rand.Seed(time.Now().UTC().UnixNano()) 125 | const chars = "0123456789abcdef" 126 | result := make([]byte, strlen) 127 | for i := 0; i < strlen; i++ { 128 | result[i] = chars[rand.Intn(len(chars))] 129 | } 130 | return string(result) 131 | } 132 | 133 | func getTestSegment() string { 134 | t := time.Now().Unix() 135 | hextime := fmt.Sprintf("%X", t) 136 | traceID := "1-" + hextime + "-" + randomString(24) 137 | message := fmt.Sprintf("{\"trace_id\": \"%s\", \"id\": \"%s\", \"start_time\": 1461096053.37518, "+ 138 | "\"end_time\": 1461096053.4042, "+ 139 | "\"name\": \"hello-1.mbfzqxzcpe.us-east-1.elasticbeanstalk.com\"}", 140 | traceID, 141 | randomString(16)) 142 | return message 143 | } 144 | 145 | // Benchmarks 146 | func BenchmarkDefaultEmitter_packSegments(b *testing.B) { 147 | seg := &Segment{} 148 | subSeg := &Segment{} 149 | subSeg.parent = seg 150 | seg.ParentSegment = seg 151 | subSeg.ParentSegment = seg.ParentSegment 152 | seg.Sampled = true 153 | seg.totalSubSegments = 22 154 | 155 | for i := 0; i < 22; i++ { 156 | seg.rawSubsegments = append(seg.rawSubsegments, subSeg) 157 | } 158 | 159 | for i := 0; i < b.N; i++ { 160 | packSegments(seg, nil) 161 | } 162 | } 163 | 164 | func BenchmarkDefaultEmitter(b *testing.B) { 165 | seg := &Segment{ 166 | ParentSegment: &Segment{ 167 | Sampled: false, 168 | }, 169 | } 170 | b.RunParallel(func(pb *testing.PB) { 171 | emitter, err := NewDefaultEmitter(&net.UDPAddr{ 172 | IP: net.IPv4(127, 0, 0, 1), 173 | Port: 2000, 174 | }) 175 | if err != nil { 176 | b.Fatal(err) 177 | } 178 | for pb.Next() { 179 | emitter.Emit(seg) 180 | } 181 | }) 182 | } 183 | 184 | func TestDefaultEmitterWithPanic(t *testing.T) { 185 | seg := &Segment{ 186 | ParentSegment: nil, // cause Panic 187 | } 188 | emitter, err := NewDefaultEmitter(&net.UDPAddr{ 189 | IP: net.IPv4(127, 0, 0, 1), 190 | Port: 3000, 191 | }) 192 | if err != nil { 193 | t.Fatal(err) 194 | } 195 | emitter.Emit(seg) 196 | } 197 | -------------------------------------------------------------------------------- /xray/default_streaming_strategy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package xray 10 | 11 | import ( 12 | "encoding/json" 13 | "errors" 14 | "sync/atomic" 15 | 16 | "github.com/aws/aws-xray-sdk-go/v2/internal/logger" 17 | ) 18 | 19 | var defaultMaxSubsegmentCount uint32 = 20 20 | 21 | // DefaultStreamingStrategy provides a default value of 20 22 | // for the maximum number of subsegments that can be emitted 23 | // in a single UDP packet. 24 | type DefaultStreamingStrategy struct { 25 | MaxSubsegmentCount uint32 26 | } 27 | 28 | // NewDefaultStreamingStrategy initializes and returns a 29 | // pointer to an instance of DefaultStreamingStrategy. 30 | func NewDefaultStreamingStrategy() (*DefaultStreamingStrategy, error) { 31 | return &DefaultStreamingStrategy{MaxSubsegmentCount: defaultMaxSubsegmentCount}, nil 32 | } 33 | 34 | // NewDefaultStreamingStrategyWithMaxSubsegmentCount initializes 35 | // and returns a pointer to an instance of DefaultStreamingStrategy 36 | // with a custom maximum number of subsegments per UDP packet. 37 | func NewDefaultStreamingStrategyWithMaxSubsegmentCount(maxSubsegmentCount int) (*DefaultStreamingStrategy, error) { 38 | if maxSubsegmentCount <= 0 { 39 | return nil, errors.New("maxSubsegmentCount must be a non-negative integer") 40 | } 41 | c := uint32(maxSubsegmentCount) 42 | return &DefaultStreamingStrategy{MaxSubsegmentCount: c}, nil 43 | } 44 | 45 | // RequiresStreaming returns true when the number of subsegment 46 | // children for a given segment is larger than MaxSubsegmentCount. 47 | func (dSS *DefaultStreamingStrategy) RequiresStreaming(seg *Segment) bool { 48 | if seg.ParentSegment.Sampled { 49 | return atomic.LoadUint32(&seg.ParentSegment.totalSubSegments) > dSS.MaxSubsegmentCount 50 | } 51 | return false 52 | } 53 | 54 | // StreamCompletedSubsegments separates subsegments from the provided 55 | // segment tree and sends them to daemon as streamed subsegment UDP packets. 56 | func (dSS *DefaultStreamingStrategy) StreamCompletedSubsegments(seg *Segment) [][]byte { 57 | logger.Debug("Beginning to stream subsegments.") 58 | var outSegments [][]byte 59 | for i := 0; i < len(seg.rawSubsegments); i++ { 60 | child := seg.rawSubsegments[i] 61 | seg.rawSubsegments[i] = seg.rawSubsegments[len(seg.rawSubsegments)-1] 62 | seg.rawSubsegments[len(seg.rawSubsegments)-1] = nil 63 | seg.rawSubsegments = seg.rawSubsegments[:len(seg.rawSubsegments)-1] 64 | 65 | seg.Subsegments[i] = seg.Subsegments[len(seg.Subsegments)-1] 66 | seg.Subsegments[len(seg.Subsegments)-1] = nil 67 | seg.Subsegments = seg.Subsegments[:len(seg.Subsegments)-1] 68 | 69 | atomic.AddUint32(&seg.ParentSegment.totalSubSegments, ^uint32(0)) 70 | 71 | // Add extra information into child subsegment 72 | child.Lock() 73 | child.beforeEmitSubsegment(seg) 74 | cb, err := json.Marshal(child) 75 | if err != nil { 76 | logger.Errorf("JSON error while marshalling subsegment: %v", err) 77 | } 78 | outSegments = append(outSegments, cb) 79 | logger.Debugf("Streaming subsegment named '%s' from segment tree.", child.Name) 80 | child.Unlock() 81 | 82 | break 83 | } 84 | logger.Debug("Finished streaming subsegments.") 85 | return outSegments 86 | } 87 | -------------------------------------------------------------------------------- /xray/default_streaming_strategy_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package xray 10 | 11 | import ( 12 | "testing" 13 | 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | func TestDefaultStreamingStrategyMaxSegmentSize(t *testing.T) { 18 | dss, _ := NewDefaultStreamingStrategy() 19 | assert.Equal(t, dss.MaxSubsegmentCount, defaultMaxSubsegmentCount) 20 | } 21 | 22 | func TestDefaultStreamingStrategyMaxSegmentSizeParameterValidation(t *testing.T) { 23 | dss, e := NewDefaultStreamingStrategyWithMaxSubsegmentCount(-1) 24 | 25 | assert.Nil(t, dss) 26 | assert.Error(t, e, "maxSubsegmentCount must be a non-negative integer") 27 | } 28 | -------------------------------------------------------------------------------- /xray/emitter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package xray 10 | 11 | import "net" 12 | 13 | // Emitter provides an interface for implementing emitting trace entities. 14 | type Emitter interface { 15 | Emit(seg *Segment) 16 | RefreshEmitterWithAddress(raddr *net.UDPAddr) 17 | } 18 | -------------------------------------------------------------------------------- /xray/fasthttp.go: -------------------------------------------------------------------------------- 1 | package xray 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "net/url" 8 | 9 | "github.com/aws/aws-xray-sdk-go/v2/header" 10 | "github.com/valyala/fasthttp" 11 | ) 12 | 13 | type FastHTTPHandler interface { 14 | Handler(SegmentNamer, fasthttp.RequestHandler) fasthttp.RequestHandler 15 | } 16 | 17 | type fasthttpHandler struct { 18 | cfg *Config 19 | } 20 | 21 | // NewFastHTTPInstrumentor returns a struct that provides Handle method 22 | // that satisfy fasthttp.RequestHandler interface. 23 | func NewFastHTTPInstrumentor(cfg *Config) FastHTTPHandler { 24 | return &fasthttpHandler{ 25 | cfg: cfg, 26 | } 27 | } 28 | 29 | // Handler wraps the provided fasthttp.RequestHandler 30 | func (h *fasthttpHandler) Handler(sn SegmentNamer, handler fasthttp.RequestHandler) fasthttp.RequestHandler { 31 | return func(ctx *fasthttp.RequestCtx) { 32 | auxCtx := context.Background() 33 | if h.cfg != nil { 34 | auxCtx = context.WithValue(context.Background(), fasthttpContextConfigKey, h.cfg) 35 | ctx.SetUserValue(fasthttpContextConfigKey, h.cfg) 36 | } 37 | 38 | name := sn.Name(string(ctx.Request.Host())) 39 | traceHeader := header.FromString(string(ctx.Request.Header.Peek(TraceIDHeaderKey))) 40 | 41 | req, err := fasthttpToNetHTTPRequest(ctx) 42 | if err != nil { 43 | ctx.Logger().Printf("%s", err.Error()) 44 | ctx.Error("Internal Server Error", fasthttp.StatusInternalServerError) 45 | return 46 | } 47 | 48 | _, seg := NewSegmentFromHeader(auxCtx, name, req, traceHeader) 49 | defer seg.Close(nil) 50 | 51 | ctx.SetUserValue(fasthttpContextKey, seg) 52 | httpCaptureRequest(seg, req) 53 | fasthttpTrace(seg, handler, ctx, traceHeader) 54 | } 55 | } 56 | 57 | // fasthttpToNetHTTPRequest convert a fasthttp.Request to http.Request 58 | func fasthttpToNetHTTPRequest(ctx *fasthttp.RequestCtx) (*http.Request, error) { 59 | requestURI := string(ctx.RequestURI()) 60 | rURL, err := url.ParseRequestURI(requestURI) 61 | if err != nil { 62 | return nil, fmt.Errorf("cannot parse requestURI %q: %s", requestURI, err) 63 | } 64 | 65 | req := &http.Request{ 66 | URL: rURL, 67 | Host: string(ctx.Host()), 68 | RequestURI: requestURI, 69 | Method: string(ctx.Method()), 70 | RemoteAddr: ctx.RemoteAddr().String(), 71 | } 72 | 73 | hdr := make(http.Header) 74 | ctx.Request.Header.VisitAll(func(k, v []byte) { 75 | sk := string(k) 76 | sv := string(v) 77 | switch sk { 78 | case "Transfer-Encoding": 79 | req.TransferEncoding = append(req.TransferEncoding, sv) 80 | default: 81 | hdr.Set(sk, sv) 82 | } 83 | }) 84 | 85 | req.Header = hdr 86 | req.TLS = ctx.TLSConnectionState() 87 | return req, nil 88 | } 89 | 90 | func fasthttpTrace(seg *Segment, h fasthttp.RequestHandler, ctx *fasthttp.RequestCtx, traceHeader *header.Header) { 91 | ctx.Request.Header.Set(TraceIDHeaderKey, generateTraceIDHeaderValue(seg, traceHeader)) 92 | h(ctx) 93 | 94 | seg.Lock() 95 | seg.GetHTTP().GetResponse().ContentLength = ctx.Response.Header.ContentLength() 96 | seg.Unlock() 97 | HttpCaptureResponse(seg, ctx.Response.StatusCode()) 98 | } 99 | -------------------------------------------------------------------------------- /xray/fasthttp_test.go: -------------------------------------------------------------------------------- 1 | package xray 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/valyala/fasthttp" 10 | ) 11 | 12 | func TestFastHTTPHandler(t *testing.T) { 13 | ctx1, td := NewTestDaemon() 14 | cfg := GetRecorder(ctx1) 15 | defer td.Close() 16 | 17 | fh := NewFastHTTPInstrumentor(cfg) 18 | handler := fh.Handler(NewFixedSegmentNamer("test"), func(ctx *fasthttp.RequestCtx) {}) 19 | rc := genericRequestCtx() 20 | handler(rc) 21 | 22 | seg, err := td.Recv() 23 | if !assert.NoError(t, err) { 24 | return 25 | } 26 | 27 | assert.Equal(t, http.StatusOK, rc.Response.StatusCode()) 28 | assert.Equal(t, http.MethodPost, seg.HTTP.Request.Method) 29 | assert.Equal(t, "http://localhost/path", seg.HTTP.Request.URL) 30 | assert.Equal(t, "1.2.3.5", seg.HTTP.Request.ClientIP) 31 | assert.Equal(t, "UA_test", seg.HTTP.Request.UserAgent) 32 | } 33 | 34 | // genericRequestCtx helper function to build fasthttp.RequestCtx 35 | func genericRequestCtx() *fasthttp.RequestCtx { 36 | b := `{"body": "content"}` 37 | req := fasthttp.Request{} 38 | req.SetBodyString(b) 39 | req.SetRequestURI("/path") 40 | req.SetHost("localhost") 41 | req.Header.SetContentType("application/json") 42 | req.Header.SetContentLength(len(b)) 43 | req.Header.SetMethod(http.MethodPost) 44 | req.Header.SetUserAgent("UA_test") 45 | 46 | rc := &fasthttp.RequestCtx{} 47 | rc.Init(&req, nil, nil) 48 | 49 | remoteAddr := &net.TCPAddr{ 50 | IP: []byte{1, 2, 3, 5}, 51 | Port: 0, 52 | } 53 | rc.SetRemoteAddr(remoteAddr) 54 | 55 | return rc 56 | } 57 | 58 | func BenchmarkFasthttpHandler(b *testing.B) { 59 | ctx1, td := NewTestDaemon() 60 | cfg := GetRecorder(ctx1) 61 | defer td.Close() 62 | 63 | fh := NewFastHTTPInstrumentor(cfg) 64 | handler := fh.Handler(NewFixedSegmentNamer("test"), func(ctx *fasthttp.RequestCtx) {}) 65 | 66 | for i := 0; i < b.N; i++ { 67 | rc := genericRequestCtx() 68 | handler(rc) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /xray/handler_go113_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | // +build go1.13 10 | 11 | package xray 12 | 13 | import ( 14 | "context" 15 | "net" 16 | "net/http" 17 | "net/http/httptest" 18 | "strings" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestRootHandler(t *testing.T) { 25 | ctx, td := NewTestDaemon() 26 | defer td.Close() 27 | 28 | handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 29 | w.WriteHeader(http.StatusOK) 30 | if _, err := w.Write([]byte(`200 - OK`)); err != nil { 31 | panic(err) 32 | } 33 | }) 34 | 35 | ts := httptest.NewUnstartedServer(Handler(NewFixedSegmentNamer("test"), handler)) 36 | ts.Config.BaseContext = func(_ net.Listener) context.Context { 37 | return ctx 38 | } 39 | ts.Start() 40 | defer ts.Close() 41 | 42 | req, err := http.NewRequest(http.MethodPost, ts.URL, strings.NewReader("")) 43 | if !assert.NoError(t, err) { 44 | return 45 | } 46 | req.Header.Set("User-Agent", "UnitTest") 47 | 48 | resp, err := http.DefaultClient.Do(req) 49 | if !assert.NoError(t, err) { 50 | return 51 | } 52 | defer resp.Body.Close() 53 | assert.Equal(t, http.StatusOK, resp.StatusCode) 54 | 55 | seg, err := td.Recv() 56 | if !assert.NoError(t, err) { 57 | return 58 | } 59 | 60 | assert.Equal(t, http.StatusOK, seg.HTTP.Response.Status) 61 | assert.Equal(t, http.MethodPost, seg.HTTP.Request.Method) 62 | assert.Equal(t, ts.URL+"/", seg.HTTP.Request.URL) 63 | assert.Equal(t, "127.0.0.1", seg.HTTP.Request.ClientIP) 64 | assert.Equal(t, "UnitTest", seg.HTTP.Request.UserAgent) 65 | } 66 | 67 | func TestNonRootHandler(t *testing.T) { 68 | ctx, td := NewTestDaemon() 69 | defer td.Close() 70 | 71 | handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 72 | w.WriteHeader(http.StatusOK) 73 | }) 74 | 75 | ts := httptest.NewUnstartedServer(Handler(NewFixedSegmentNamer("test"), handler)) 76 | ts.Config.BaseContext = func(_ net.Listener) context.Context { 77 | return ctx 78 | } 79 | ts.Start() 80 | defer ts.Close() 81 | 82 | req, err := http.NewRequest(http.MethodPost, ts.URL, strings.NewReader("")) 83 | if !assert.NoError(t, err) { 84 | return 85 | } 86 | req.Header.Set("User-Agent", "UnitTest") 87 | req.Header.Set(TraceIDHeaderKey, "Root=fakeid; Parent=reqid; Sampled=1") 88 | 89 | resp, err := http.DefaultClient.Do(req) 90 | if !assert.NoError(t, err) { 91 | return 92 | } 93 | defer resp.Body.Close() 94 | assert.Equal(t, http.StatusOK, resp.StatusCode) 95 | 96 | seg, err := td.Recv() 97 | if !assert.NoError(t, err) { 98 | return 99 | } 100 | 101 | assert.Equal(t, "fakeid", seg.TraceID) 102 | assert.Equal(t, "reqid", seg.ParentID) 103 | assert.Equal(t, true, seg.Sampled) 104 | } 105 | -------------------------------------------------------------------------------- /xray/lambda.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package xray 10 | 11 | import ( 12 | "context" 13 | "os" 14 | "path/filepath" 15 | "time" 16 | 17 | "github.com/aws/aws-xray-sdk-go/v2/header" 18 | "github.com/aws/aws-xray-sdk-go/v2/internal/logger" 19 | ) 20 | 21 | // LambdaTraceHeaderKey is key to get trace header from context. 22 | const LambdaTraceHeaderKey string = "x-amzn-trace-id" 23 | 24 | // LambdaTaskRootKey is the key to get Lambda Task Root from environment variable. 25 | const LambdaTaskRootKey string = "LAMBDA_TASK_ROOT" 26 | 27 | // SDKInitializedFileFolder records the location of SDK initialized file. 28 | const SDKInitializedFileFolder string = "/tmp/.aws-xray" 29 | 30 | // SDKInitializedFileName records the SDK initialized file name. 31 | const SDKInitializedFileName string = "initialized" 32 | 33 | func getTraceHeaderFromContext(ctx context.Context) *header.Header { 34 | var traceHeader string 35 | 36 | if traceHeaderValue := ctx.Value(LambdaTraceHeaderKey); traceHeaderValue != nil { 37 | traceHeader = traceHeaderValue.(string) 38 | return header.FromString(traceHeader) 39 | } 40 | return nil 41 | } 42 | 43 | func newFacadeSegment(ctx context.Context) (context.Context, *Segment) { 44 | traceHeader := getTraceHeaderFromContext(ctx) 45 | return BeginFacadeSegment(ctx, "facade", traceHeader) 46 | } 47 | 48 | func getLambdaTaskRoot() string { 49 | return os.Getenv(LambdaTaskRootKey) 50 | } 51 | 52 | func initLambda() { 53 | if getLambdaTaskRoot() != "" { 54 | now := time.Now() 55 | filePath, err := createFile(SDKInitializedFileFolder, SDKInitializedFileName) 56 | if err != nil { 57 | logger.Debugf("unable to create file at %s. failed to signal SDK initialization with error: %v", filePath, err) 58 | } else { 59 | e := os.Chtimes(filePath, now, now) 60 | if e != nil { 61 | logger.Debugf("unable to write to %s. failed to signal SDK initialization with error: %v", filePath, e) 62 | } 63 | } 64 | } 65 | } 66 | 67 | func createFile(dir string, name string) (string, error) { 68 | fileDir := filepath.FromSlash(dir) 69 | filePath := fileDir + string(os.PathSeparator) + name 70 | 71 | // detect if file exists 72 | var _, err = os.Stat(filePath) 73 | 74 | // create file if not exists 75 | if os.IsNotExist(err) { 76 | e := os.MkdirAll(dir, os.ModePerm) 77 | if e != nil { 78 | return filePath, e 79 | } 80 | var file, err = os.Create(filePath) 81 | if err != nil { 82 | return filePath, err 83 | } 84 | file.Close() 85 | return filePath, nil 86 | } else if err != nil { 87 | return filePath, err 88 | } 89 | 90 | return filePath, nil 91 | } 92 | -------------------------------------------------------------------------------- /xray/lambda_test.go: -------------------------------------------------------------------------------- 1 | package xray 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "net/http/httptest" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/aws/aws-xray-sdk-go/v2/header" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | const ExampleTraceHeader string = "Root=1-57ff426a-80c11c39b0c928905eb0828d;Parent=1234abcd1234abcd;Sampled=1" 15 | 16 | func TestLambdaSegmentEmit(t *testing.T) { 17 | ctx, td := NewTestDaemon() 18 | defer td.Close() 19 | 20 | // go-lint warns "should not use basic type string as key in context.WithValue", 21 | // but it must be string type because the trace header comes from aws/aws-lambda-go. 22 | // https://github.com/aws/aws-lambda-go/blob/b5b7267d297de263cc5b61f8c37543daa9c95ffd/lambda/function.go#L65 23 | ctx = context.WithValue(ctx, LambdaTraceHeaderKey, "Root=fakeid; Parent=reqid; Sampled=1") 24 | _, subseg := BeginSubsegment(ctx, "test-lambda") 25 | subseg.Close(nil) 26 | 27 | seg, e := td.Recv() 28 | assert.NoError(t, e) 29 | assert.Equal(t, "fakeid", seg.TraceID) 30 | assert.Equal(t, "reqid", seg.ParentID) 31 | assert.Equal(t, true, seg.Sampled) 32 | assert.Equal(t, "subsegment", seg.Type) 33 | } 34 | 35 | func TestLambdaMix(t *testing.T) { 36 | // Setup 37 | ctx, td := NewTestDaemon() 38 | defer td.Close() 39 | ctx = context.WithValue(ctx, LambdaTraceHeaderKey, ExampleTraceHeader) 40 | 41 | // First 42 | ctx1, _ := BeginSubsegment(ctx, "test-lambda-1") 43 | testHelper(ctx1, t, td, true) 44 | 45 | // Second 46 | ctx2, _ := BeginSubsegmentWithoutSampling(ctx, "test-lambda-2") 47 | testHelper(ctx2, t, td, false) 48 | 49 | // Third 50 | ctx3, _ := BeginSubsegment(ctx, "test-lambda-3") 51 | testHelper(ctx3, t, td, true) 52 | 53 | // Forth 54 | ctx4, _ := BeginSubsegmentWithoutSampling(ctx, "test-lambda-4") 55 | testHelper(ctx4, t, td, false) 56 | } 57 | 58 | /* 59 | This helper function creates a request and validates the response using the context provided. 60 | */ 61 | func testHelper(ctx context.Context, t *testing.T, td *TestDaemon, sampled bool) { 62 | var subseg = GetSegment(ctx) 63 | 64 | handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 65 | w.WriteHeader(http.StatusOK) 66 | if _, err := w.Write([]byte(`200 - OK`)); err != nil { 67 | panic(err) 68 | } 69 | }) 70 | 71 | // Create Test Server 72 | ts := httptest.NewServer(HandlerWithContext(context.Background(), NewFixedSegmentNamer("RequestSegment"), handler)) 73 | defer ts.Close() 74 | 75 | // Perform Request 76 | req, _ := http.NewRequestWithContext(ctx, http.MethodPost, ts.URL, strings.NewReader("")) 77 | req.Header.Add(TraceIDHeaderKey, generateHeader(subseg).String()) 78 | resp, _ := http.DefaultClient.Do(req) 79 | 80 | // Close the test server down 81 | ts.Close() 82 | 83 | // Ensure the return value is valid 84 | assert.Equal(t, http.StatusOK, resp.StatusCode) 85 | 86 | assert.Equal(t, subseg.TraceID, header.FromString(resp.Header.Get("x-amzn-trace-id")).TraceID) 87 | assert.Equal(t, subseg.ID, header.FromString(resp.Header.Get("x-amzn-trace-id")).ParentID) 88 | 89 | subseg.Close(nil) 90 | emittedSeg, e := td.Recv() 91 | 92 | if sampled { 93 | assert.Equal(t, header.Sampled, header.FromString(resp.Header.Get("x-amzn-trace-id")).SamplingDecision) 94 | assert.NoError(t, e) 95 | assert.Equal(t, true, emittedSeg.Sampled) 96 | assert.Equal(t, subseg.Name, emittedSeg.Name) 97 | } else { 98 | assert.Equal(t, header.NotSampled, header.FromString(resp.Header.Get("x-amzn-trace-id")).SamplingDecision) 99 | assert.Equal(t, (*Segment)(nil), emittedSeg) 100 | } 101 | } 102 | 103 | func generateHeader(seg *Segment) header.Header { 104 | var samplingDecision = header.Sampled 105 | if !seg.Sampled { 106 | samplingDecision = header.NotSampled 107 | } 108 | 109 | return header.Header{ 110 | TraceID: seg.TraceID, 111 | ParentID: seg.ID, 112 | SamplingDecision: samplingDecision, 113 | 114 | AdditionalData: make(map[string]string), 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /xray/sql_go110.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | // +build go1.10 10 | 11 | package xray 12 | 13 | import ( 14 | "context" 15 | "database/sql/driver" 16 | "sync" 17 | ) 18 | 19 | // SQLConnector wraps the connector, and traces SQL executions. 20 | // Unlike SQLContext, SQLConnector doesn't filter the password of the dsn. 21 | // So, you have to filter the password before passing the dsn to SQLConnector. 22 | func SQLConnector(dsn string, connector driver.Connector) driver.Connector { 23 | d := &driverDriver{ 24 | Driver: connector.Driver(), 25 | baseName: "unknown", 26 | } 27 | return &driverConnector{ 28 | Connector: connector, 29 | driver: d, 30 | name: dsn, 31 | filtered: true, 32 | // initialized attr lazy because we have no context here. 33 | } 34 | } 35 | 36 | func (conn *driverConn) ResetSession(ctx context.Context) error { 37 | if sr, ok := conn.Conn.(driver.SessionResetter); ok { 38 | return sr.ResetSession(ctx) 39 | } 40 | return nil 41 | } 42 | 43 | type driverConnector struct { 44 | driver.Connector 45 | driver *driverDriver 46 | filtered bool 47 | name string 48 | 49 | mu sync.RWMutex 50 | attr *dbAttribute 51 | } 52 | 53 | func (c *driverConnector) Connect(ctx context.Context) (driver.Conn, error) { 54 | var rawConn driver.Conn 55 | attr, err := c.getAttr(ctx) 56 | if err != nil { 57 | return nil, err 58 | } 59 | err = Capture(ctx, attr.dbname+attr.host, func(ctx context.Context) error { 60 | attr.populate(ctx, "CONNECT") 61 | var err error 62 | rawConn, err = c.Connector.Connect(ctx) 63 | return err 64 | }) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | conn := &driverConn{ 70 | Conn: rawConn, 71 | attr: attr, 72 | } 73 | return conn, nil 74 | } 75 | 76 | func (c *driverConnector) getAttr(ctx context.Context) (*dbAttribute, error) { 77 | c.mu.RLock() 78 | attr := c.attr 79 | c.mu.RUnlock() 80 | if attr != nil { 81 | return attr, nil 82 | } 83 | 84 | c.mu.Lock() 85 | defer c.mu.Unlock() 86 | if c.attr != nil { 87 | return c.attr, nil 88 | } 89 | conn, err := c.Connector.Connect(ctx) 90 | if err != nil { 91 | return nil, err 92 | } 93 | defer conn.Close() 94 | 95 | attr, err = newDBAttribute(ctx, c.driver.baseName, c.driver.Driver, conn, c.name, c.filtered) 96 | if err != nil { 97 | return nil, err 98 | } 99 | c.attr = attr 100 | return attr, nil 101 | } 102 | 103 | func (c *driverConnector) Driver() driver.Driver { 104 | return c.driver 105 | } 106 | 107 | type fallbackConnector struct { 108 | driver driver.Driver 109 | name string 110 | } 111 | 112 | func (c *fallbackConnector) Connect(ctx context.Context) (driver.Conn, error) { 113 | conn, err := c.driver.Open(c.name) 114 | if err != nil { 115 | return nil, err 116 | } 117 | select { 118 | default: 119 | case <-ctx.Done(): 120 | conn.Close() 121 | return nil, ctx.Err() 122 | } 123 | return conn, nil 124 | } 125 | 126 | func (c *fallbackConnector) Driver() driver.Driver { 127 | return c.driver 128 | } 129 | 130 | func (d *driverDriver) OpenConnector(name string) (driver.Connector, error) { 131 | var c driver.Connector 132 | if dctx, ok := d.Driver.(driver.DriverContext); ok { 133 | var err error 134 | c, err = dctx.OpenConnector(name) 135 | if err != nil { 136 | return nil, err 137 | } 138 | } else { 139 | c = &fallbackConnector{ 140 | driver: d.Driver, 141 | name: name, 142 | } 143 | } 144 | c = &driverConnector{ 145 | Connector: c, 146 | driver: d, 147 | filtered: false, 148 | name: name, 149 | // initialized attr lazy because we have no context here. 150 | } 151 | return c, nil 152 | } 153 | -------------------------------------------------------------------------------- /xray/sql_go110_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | // +build go1.10 10 | 11 | package xray 12 | 13 | import ( 14 | "database/sql" 15 | "database/sql/driver" 16 | "encoding/json" 17 | "testing" 18 | 19 | "github.com/DATA-DOG/go-sqlmock" 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | type versionedDriver struct { 24 | driver.Driver 25 | version string 26 | } 27 | 28 | func (d *versionedDriver) Version() string { 29 | return d.version 30 | } 31 | 32 | func TestDriverVersion(t *testing.T) { 33 | dsn := "test-versioned-driver" 34 | db, mock, err := sqlmock.NewWithDSN(dsn) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | defer db.Close() 39 | mockPostgreSQL(mock, nil) 40 | 41 | // implement versionedDriver 42 | driver := &versionedDriver{ 43 | Driver: db.Driver(), 44 | version: "3.1415926535", 45 | } 46 | connector := &fallbackConnector{ 47 | driver: driver, 48 | name: dsn, 49 | } 50 | sqlConnector := SQLConnector("sanitized-dsn", connector) 51 | db = sql.OpenDB(sqlConnector) 52 | defer db.Close() 53 | 54 | ctx, td := NewTestDaemon() 55 | defer td.Close() 56 | 57 | // Execute SQL 58 | ctx, root := BeginSegment(ctx, "test") 59 | if err := db.PingContext(ctx); err != nil { 60 | t.Fatal(err) 61 | } 62 | root.Close(nil) 63 | assert.NoError(t, mock.ExpectationsWereMet()) 64 | 65 | // assertion 66 | seg, err := td.Recv() 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | var subseg *Segment 71 | if err := json.Unmarshal(seg.Subsegments[0], &subseg); err != nil { 72 | t.Fatal(err) 73 | } 74 | assert.Equal(t, "sanitized-dsn", subseg.SQL.ConnectionString) 75 | assert.Equal(t, "3.1415926535", subseg.SQL.DriverVersion) 76 | } 77 | -------------------------------------------------------------------------------- /xray/sql_go111_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | // +build go1.11 10 | 11 | package xray 12 | 13 | import ( 14 | "testing" 15 | 16 | "github.com/DATA-DOG/go-sqlmock" 17 | "github.com/stretchr/testify/assert" 18 | ) 19 | 20 | func TestMySQLPasswordConnectionString(t *testing.T) { 21 | tc := []struct { 22 | dsn string 23 | url string 24 | str string 25 | }{ 26 | { 27 | dsn: "username:password@protocol(address:1234)/dbname?param=value", 28 | url: "username@protocol(address:1234)/dbname?param=value", 29 | str: "username@protocol(address:1234)/dbname?param=value", 30 | }, 31 | { 32 | dsn: "username@protocol(address:1234)/dbname?param=value", 33 | url: "username@protocol(address:1234)/dbname?param=value", 34 | str: "username@protocol(address:1234)/dbname?param=value", 35 | }, 36 | } 37 | 38 | for _, tt := range tc { 39 | tt := tt 40 | t.Run(tt.dsn, func(t *testing.T) { 41 | db, mock, err := sqlmock.NewWithDSN(tt.dsn) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | defer db.Close() 46 | mockMySQL(mock, nil) 47 | 48 | subseg, err := capturePing(tt.dsn) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | assert.NoError(t, mock.ExpectationsWereMet()) 53 | 54 | assert.Equal(t, "remote", subseg.Namespace) 55 | assert.Equal(t, "MySQL", subseg.SQL.DatabaseType) 56 | if subseg.SQL.URL != "" { 57 | assert.Equal(t, tt.url, subseg.SQL.URL) 58 | } 59 | if subseg.SQL.ConnectionString != "" { 60 | assert.Equal(t, tt.str, subseg.SQL.ConnectionString) 61 | } 62 | }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /xray/sql_prego111_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | // +build !go1.11 10 | 11 | package xray 12 | 13 | import ( 14 | "testing" 15 | 16 | "github.com/DATA-DOG/go-sqlmock" 17 | "github.com/stretchr/testify/assert" 18 | ) 19 | 20 | func TestMySQLPasswordConnectionString(t *testing.T) { 21 | tc := []struct { 22 | dsn string 23 | url string 24 | str string 25 | }{ 26 | { 27 | dsn: "username:password@protocol(address:1234)/dbname?param=value", 28 | url: "username@protocol(address:1234)/dbname?param=value", 29 | }, 30 | { 31 | dsn: "username@protocol(address:1234)/dbname?param=value", 32 | url: "username@protocol(address:1234)/dbname?param=value", 33 | }, 34 | } 35 | 36 | for _, tt := range tc { 37 | tt := tt 38 | t.Run(tt.dsn, func(t *testing.T) { 39 | db, mock, err := sqlmock.NewWithDSN(tt.dsn) 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | defer db.Close() 44 | mockMySQL(mock, nil) 45 | 46 | subseg, err := capturePing(tt.dsn) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | assert.NoError(t, mock.ExpectationsWereMet()) 51 | 52 | assert.Equal(t, "remote", subseg.Namespace) 53 | assert.Equal(t, "MySQL", subseg.SQL.DatabaseType) 54 | assert.Equal(t, tt.url, subseg.SQL.URL) 55 | assert.Equal(t, tt.str, subseg.SQL.ConnectionString) 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /xray/streaming_strategy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package xray 10 | 11 | // StreamingStrategy provides an interface for implementing streaming strategies. 12 | type StreamingStrategy interface { 13 | RequiresStreaming(seg *Segment) bool 14 | StreamCompletedSubsegments(seg *Segment) [][]byte 15 | } 16 | -------------------------------------------------------------------------------- /xray/util_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package xray 10 | 11 | import ( 12 | "bytes" 13 | "context" 14 | "encoding/json" 15 | "fmt" 16 | "net" 17 | "net/http" 18 | "sync" 19 | "time" 20 | 21 | "github.com/aws/aws-xray-sdk-go/v2/header" 22 | ) 23 | 24 | func NewTestDaemon() (context.Context, *TestDaemon) { 25 | c := make(chan *result, 200) 26 | conn, err := net.ListenPacket("udp", "127.0.0.1:0") 27 | if err != nil { 28 | if conn, err = net.ListenPacket("udp6", "[::1]:0"); err != nil { 29 | panic(fmt.Sprintf("xray: failed to listen: %v", err)) 30 | } 31 | } 32 | ctx, cancel := context.WithCancel(context.Background()) 33 | d := &TestDaemon{ 34 | ch: c, 35 | conn: conn, 36 | ctx: ctx, 37 | cancel: cancel, 38 | } 39 | emitter, err := NewDefaultEmitter(conn.LocalAddr().(*net.UDPAddr)) 40 | if err != nil { 41 | panic(fmt.Sprintf("xray: failed to created emitter: %v", err)) 42 | } 43 | 44 | ctx, err = ContextWithConfig(ctx, Config{ 45 | Emitter: emitter, 46 | DaemonAddr: conn.LocalAddr().String(), 47 | ServiceVersion: "TestVersion", 48 | SamplingStrategy: &TestSamplingStrategy{}, 49 | ContextMissingStrategy: &TestContextMissingStrategy{}, 50 | StreamingStrategy: &TestStreamingStrategy{}, 51 | }) 52 | if err != nil { 53 | panic(fmt.Sprintf("xray: failed to configure: %v", err)) 54 | } 55 | go d.run(c) 56 | return ctx, d 57 | } 58 | 59 | type TestDaemon struct { 60 | ch <-chan *result 61 | conn net.PacketConn 62 | ctx context.Context 63 | cancel context.CancelFunc 64 | closeOnce sync.Once 65 | } 66 | 67 | type result struct { 68 | Segment *Segment 69 | Error error 70 | } 71 | 72 | func (td *TestDaemon) Close() { 73 | td.closeOnce.Do(func() { 74 | td.cancel() 75 | td.conn.Close() 76 | }) 77 | } 78 | 79 | func (td *TestDaemon) run(c chan *result) { 80 | buffer := make([]byte, 64*1024) 81 | for { 82 | n, _, err := td.conn.ReadFrom(buffer) 83 | if err != nil { 84 | select { 85 | case c <- &result{nil, err}: 86 | case <-td.ctx.Done(): 87 | return 88 | } 89 | continue 90 | } 91 | 92 | idx := bytes.IndexByte(buffer, '\n') 93 | buffered := buffer[idx+1 : n] 94 | 95 | seg := &Segment{} 96 | err = json.Unmarshal(buffered, &seg) 97 | if err != nil { 98 | select { 99 | case c <- &result{nil, err}: 100 | case <-td.ctx.Done(): 101 | return 102 | } 103 | continue 104 | } 105 | 106 | seg.Sampled = true 107 | select { 108 | case c <- &result{seg, nil}: 109 | case <-td.ctx.Done(): 110 | return 111 | } 112 | } 113 | } 114 | 115 | func (td *TestDaemon) Recv() (*Segment, error) { 116 | ctx, cancel := context.WithTimeout(td.ctx, 500*time.Millisecond) 117 | defer cancel() 118 | select { 119 | case r := <-td.ch: 120 | return r.Segment, r.Error 121 | case <-ctx.Done(): 122 | return nil, ctx.Err() 123 | } 124 | } 125 | 126 | type XRayHeaders struct { 127 | RootTraceID string 128 | ParentID string 129 | Sampled bool 130 | } 131 | 132 | func ParseHeadersForTest(h http.Header) XRayHeaders { 133 | traceHeader := header.FromString(h.Get(TraceIDHeaderKey)) 134 | 135 | return XRayHeaders{ 136 | RootTraceID: traceHeader.TraceID, 137 | ParentID: traceHeader.ParentID, 138 | Sampled: traceHeader.SamplingDecision == header.Sampled, 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /xraylog/xray_log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file 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. 8 | 9 | package xraylog 10 | 11 | import ( 12 | "fmt" 13 | "io" 14 | "sync" 15 | "time" 16 | ) 17 | 18 | // Logger is the logging interface used by xray. fmt.Stringer is used to 19 | // defer expensive serialization operations until the message is actually 20 | // logged (i.e. don't bother serializing debug messages if they aren't going 21 | // to show up). 22 | type Logger interface { 23 | // Log can be called concurrently from multiple goroutines so make sure 24 | // your implementation is goroutine safe. 25 | Log(level LogLevel, msg fmt.Stringer) 26 | } 27 | 28 | // LogLevel represents the severity of a log message, where a higher value 29 | // means more severe. The integer value should not be serialized as it is 30 | // subject to change. 31 | type LogLevel int 32 | 33 | const ( 34 | // LogLevelDebug is usually only enabled when debugging. 35 | LogLevelDebug LogLevel = iota + 1 36 | 37 | // LogLevelInfo is general operational entries about what's going on inside the application. 38 | LogLevelInfo 39 | 40 | // LogLevelWarn is non-critical entries that deserve eyes. 41 | LogLevelWarn 42 | 43 | // LogLevelError is used for errors that should definitely be noted. 44 | LogLevelError 45 | ) 46 | 47 | func (ll LogLevel) String() string { 48 | switch ll { 49 | case LogLevelDebug: 50 | return "DEBUG" 51 | case LogLevelInfo: 52 | return "INFO" 53 | case LogLevelWarn: 54 | return "WARN" 55 | case LogLevelError: 56 | return "ERROR" 57 | default: 58 | return fmt.Sprintf("UNKNOWNLOGLEVEL<%d>", ll) 59 | } 60 | } 61 | 62 | // NewDefaultLogger makes a Logger object that writes newline separated 63 | // messages to w, if the level of the message is at least minLogLevel. 64 | // The default logger synchronizes around Write() calls to the underlying 65 | // io.Writer. 66 | func NewDefaultLogger(w io.Writer, minLogLevel LogLevel) Logger { 67 | return &defaultLogger{w: w, minLevel: minLogLevel} 68 | } 69 | 70 | type defaultLogger struct { 71 | mu sync.Mutex 72 | w io.Writer 73 | minLevel LogLevel 74 | } 75 | 76 | func (l *defaultLogger) Log(ll LogLevel, msg fmt.Stringer) { 77 | if ll < l.minLevel { 78 | return 79 | } 80 | 81 | l.mu.Lock() 82 | defer l.mu.Unlock() 83 | fmt.Fprintf(l.w, "%s [%s] %s\n", time.Now().Format(time.RFC3339), ll, msg) 84 | } 85 | 86 | // NullLogger can be used to disable logging (pass to xray.SetLogger()). 87 | var NullLogger = nullLogger{} 88 | 89 | type nullLogger struct{} 90 | 91 | func (nl nullLogger) Log(ll LogLevel, msg fmt.Stringer) { 92 | } 93 | --------------------------------------------------------------------------------