├── .editorconfig ├── .envrc_example ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ ├── question-discussion.md │ └── security-vulnerability-report.md ├── PULL_REQUEST_TEMPLATE.md ├── release.yml └── workflows │ ├── add-to-project-v2.yml │ ├── apply-labels.yml │ ├── publish-ghcr.yml │ ├── stale.yml │ └── validate-pr-title.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DEVELOPING.md ├── LICENSE ├── NOTICE ├── OSSMETADATA ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── Tiltfile ├── docker-compose.dotnet.yml ├── docker-compose.elixir.yml ├── docker-compose.java.yml ├── docker-compose.node.yml ├── docker-compose.python.yml ├── docker-compose.web.yml ├── docker-compose.yml ├── dotnet ├── .editorconfig ├── .gitignore ├── README.md ├── dotnet.sln ├── frontend │ ├── Controllers │ │ └── GreetingController.cs │ ├── Dockerfile │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.Development.json │ ├── appsettings.json │ └── frontend.csproj ├── global.json ├── message-service │ ├── Controllers │ │ └── MessageController.cs │ ├── Dockerfile │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── message-service.csproj ├── name-service │ ├── Controllers │ │ └── NameController.cs │ ├── Dockerfile │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── name-service.csproj └── year-service │ ├── Dockerfile │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── appsettings.Development.json │ ├── appsettings.json │ └── year-service.csproj ├── elixir ├── .editorconfig ├── .tool-versions ├── README.md ├── frontend │ ├── .formatter.exs │ ├── .gitignore │ ├── Dockerfile │ ├── config │ │ ├── config.exs │ │ ├── dev.exs │ │ ├── prod.exs │ │ ├── prod.secret.exs │ │ └── test.exs │ ├── lib │ │ ├── frontend.ex │ │ ├── frontend │ │ │ └── application.ex │ │ ├── frontend_web.ex │ │ └── frontend_web │ │ │ ├── controllers │ │ │ └── greetings_controller.ex │ │ │ ├── endpoint.ex │ │ │ ├── router.ex │ │ │ ├── telemetry.ex │ │ │ └── views │ │ │ ├── error_helpers.ex │ │ │ └── error_view.ex │ ├── mix.exs │ └── mix.lock ├── message │ ├── .formatter.exs │ ├── .gitignore │ ├── Dockerfile │ ├── config │ │ └── runtime.exs │ ├── lib │ │ ├── message.ex │ │ └── message │ │ │ └── application.ex │ ├── mix.exs │ └── mix.lock ├── name │ ├── .formatter.exs │ ├── .gitignore │ ├── Dockerfile │ ├── config │ │ └── runtime.exs │ ├── lib │ │ ├── name.ex │ │ └── name │ │ │ └── application.ex │ ├── mix.exs │ └── mix.lock └── year │ ├── .formatter.exs │ ├── .gitignore │ ├── Dockerfile │ ├── config │ └── runtime.exs │ ├── lib │ ├── year.ex │ └── year │ │ └── application.ex │ ├── mix.exs │ └── mix.lock ├── go-auto-instrumented ├── .dockerignore ├── README.md ├── docker-compose.yml ├── frontend │ ├── Dockerfile │ └── main.go ├── go.mod ├── go.sum ├── greetings-instrumented.yaml ├── greetings │ ├── frontend.yaml │ ├── kustomization.yaml │ ├── message.yaml │ ├── name.yaml │ ├── ns.yaml │ └── year.yaml ├── message-service │ ├── Dockerfile │ └── main.go ├── name-service │ ├── Dockerfile │ └── main.go ├── otel-collector.yaml └── year-service │ ├── Dockerfile │ └── main.go ├── go-uninstrumented ├── .dockerignore ├── README.md ├── docker-compose.yml ├── frontend-service │ ├── Dockerfile │ └── main.go ├── go.mod ├── go.sum ├── greetings.yaml ├── message-service │ ├── Dockerfile │ └── main.go ├── name-service │ ├── Dockerfile │ └── main.go └── year-service │ ├── Dockerfile │ └── main.go ├── golang ├── .editorconfig ├── docker-compose.yml ├── frontend │ ├── Dockerfile │ └── main.go ├── go.mod ├── go.sum ├── message-service │ ├── Dockerfile │ └── main.go ├── name-service │ ├── Dockerfile │ └── main.go └── year-service │ ├── Dockerfile │ └── main.go ├── java ├── .editorconfig ├── frontend │ ├── .gitignore │ ├── Dockerfile │ ├── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── io │ │ │ └── honeycomb │ │ │ └── examples │ │ │ └── frontend_java │ │ │ ├── FrontendApplication.java │ │ │ ├── GreetingController.java │ │ │ ├── MessageService.java │ │ │ └── NameService.java │ │ └── resources │ │ └── application.properties ├── message-service │ ├── .gitignore │ ├── Dockerfile │ ├── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── io │ │ │ └── honeycomb │ │ │ └── examples │ │ │ └── message │ │ │ ├── MessageApplication.java │ │ │ ├── MessageEndpoints.java │ │ │ └── MessageService.java │ │ └── resources │ │ └── application.properties ├── name-service │ ├── .gitignore │ ├── Dockerfile │ ├── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── io │ │ │ └── honeycomb │ │ │ └── examples │ │ │ └── name │ │ │ ├── MessageService.java │ │ │ ├── NameApplication.java │ │ │ ├── NameController.java │ │ │ ├── NameService.java │ │ │ └── YearService.java │ │ └── resources │ │ └── application.properties └── year-service │ ├── .gitignore │ ├── Dockerfile │ ├── build.gradle │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── honeycomb │ │ │ └── examples │ │ │ └── javaotlp │ │ │ ├── YearApplication.java │ │ │ ├── YearController.java │ │ │ └── YearService.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── io │ └── honeycomb │ └── examples │ └── javaotlp │ └── YearApplicationTests.java ├── node ├── .editorconfig ├── .gitignore ├── .tool-versions ├── README.md ├── frontend-service │ ├── .dockerignore │ ├── .prettierrc.js │ ├── Dockerfile │ ├── main.js │ ├── package-lock.json │ ├── package.json │ └── tracing.js ├── message-service │ ├── .dockerignore │ ├── .prettierrc.js │ ├── Dockerfile │ ├── main.js │ ├── package-lock.json │ └── package.json ├── name-service │ ├── .dockerignore │ ├── .prettierrc.js │ ├── Dockerfile │ ├── main.js │ ├── package-lock.json │ └── package.json └── year-service │ ├── .dockerignore │ ├── .prettierrc.js │ ├── Dockerfile │ ├── main.js │ ├── package-lock.json │ ├── package.json │ └── tracing.js ├── otel-collector-config.yaml ├── python ├── .editorconfig ├── .gitignore ├── .tool-versions ├── frontend │ ├── Dockerfile │ ├── README.rst │ ├── frontend │ │ ├── __init__.py │ │ └── __main__.py │ ├── poetry.lock │ ├── pyproject.toml │ └── tests │ │ ├── __init__.py │ │ └── test_frontend.py ├── message-service │ ├── Dockerfile │ ├── README.rst │ ├── message_service │ │ ├── __init__.py │ │ └── __main__.py │ ├── poetry.lock │ ├── pyproject.toml │ └── tests │ │ ├── __init__.py │ │ └── test_message_service.py ├── name-service │ ├── .flaskenv │ ├── Dockerfile │ ├── README.rst │ ├── name_service │ │ └── __init__.py │ ├── poetry.lock │ ├── pyproject.toml │ └── tests │ │ ├── __init__.py │ │ └── test_name_service.py └── year-service │ ├── Dockerfile │ ├── README.rst │ ├── poetry.lock │ ├── pyproject.toml │ ├── tests │ ├── __init__.py │ └── test_year_service.py │ └── yearservice │ ├── manage.py │ ├── yearapp │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py │ └── yearservice │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── ruby ├── .editorconfig ├── .envrc_example ├── Tiltfile ├── docker-compose.yml ├── frontend │ ├── .ruby-version │ ├── Dockerfile │ ├── Gemfile │ ├── Gemfile.lock │ ├── frontend.ru │ └── o11y_wrapper.rb ├── message-service │ ├── .ruby-version │ ├── Dockerfile │ ├── Gemfile │ ├── Gemfile.lock │ └── message.ru ├── name-service │ ├── .ruby-version │ ├── Dockerfile │ ├── Gemfile │ ├── Gemfile.lock │ └── name.rb └── year-service │ ├── .ruby-version │ ├── Dockerfile │ ├── Gemfile │ ├── Gemfile.lock │ ├── config.ru │ └── o11y_wrapper.rb └── web ├── .eslintignore ├── .eslintrc.js ├── .prettierignore ├── .prettierrc.js ├── Dockerfile ├── README.md ├── package-lock.json ├── package.json ├── src ├── index.js └── tracing-http.js └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # When opening a file, EditorConfig plugins look for a file named .editorconfig 4 | # in the directory of the opened file and in every parent directory. 5 | # A search for .editorconfig files will stop if the root filepath is reached 6 | # or an EditorConfig file with root=true is found. 7 | 8 | # EditorConfig files are read top to bottom and the most recent rules found 9 | # take precedence. Properties from matching EditorConfig sections are applied 10 | # in the order they were read, so properties in closer files take precedence. 11 | 12 | # top-most EditorConfig file 13 | root = true 14 | 15 | [*] 16 | 17 | #### Core EditorConfig Options #### 18 | 19 | # Indentation and spacing 20 | indent_size = 4 21 | indent_style = space 22 | tab_width = 4 23 | 24 | # New line preferences 25 | end_of_line = lf 26 | insert_final_newline = true 27 | 28 | [*.md] 29 | indent_size = 2 30 | tab_width = 2 31 | insert_final_newline = true 32 | 33 | [.envrc] 34 | end_of_line = lf 35 | 36 | [.tool-versions] 37 | end_of_line = lf -------------------------------------------------------------------------------- /.envrc_example: -------------------------------------------------------------------------------- 1 | export HONEYCOMB_API_KEY= 2 | export HONEYCOMB_API=https://api.honeycomb.io 3 | export HONEYCOMB_API_ENDPOINT=${HONEYCOMB_API} 4 | export OTEL_EXPORTER_OTLP_ENDPOINT=${HONEYCOMB_API} 5 | export OTEL_EXPORTER_OTLP_HEADERS="x-honeycomb-team=${HONEYCOMB_API_KEY}" 6 | 7 | # SERVICE_NAME set in Tiltfile, set in some code directly, but may still be needed for some services 8 | 9 | ## 10 | ## classic honeycomb ## 11 | ## 12 | # export HONEYCOMB_API_KEY= 13 | # export HONEYCOMB_DATASET=greetings 14 | # export HONEYCOMB_API=https://api.honeycomb.io 15 | # export HONEYCOMB_API_ENDPOINT=${HONEYCOMB_API} 16 | # export OTEL_EXPORTER_OTLP_ENDPOINT=${HONEYCOMB_API} 17 | # export OTEL_EXPORTER_OTLP_HEADERS="x-honeycomb-team=${HONEYCOMB_API_KEY},x-honeycomb-dataset=${HONEYCOMB_DATASET}" 18 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Code owners file. 2 | # This file controls who is tagged for review for any given pull request. 3 | 4 | # For anything not explicitly taken by someone else: 5 | * @honeycombio/pipeline-team 6 | 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Let us know if something is not working as expected 4 | title: '' 5 | labels: 'type: bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 17 | 18 | **Versions** 19 | 20 | - Example: 21 | - Runtime: 22 | 23 | **Steps to reproduce** 24 | 25 | 1. 26 | 27 | **Additional context** 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'type: enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 15 | 16 | **Is your feature request related to a problem? Please describe.** 17 | 18 | 19 | **Describe the solution you'd like** 20 | 21 | 22 | **Describe alternatives you've considered** 23 | 24 | 25 | **Additional context** 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question-discussion.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question/Discussion 3 | about: General question about how things work or a discussion 4 | title: '' 5 | labels: 'type: discussion' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/security-vulnerability-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Security vulnerability report 3 | about: Let us know if you discover a security vulnerability 4 | title: '' 5 | labels: 'type: security' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 15 | **Versions** 16 | 17 | - Example: 18 | - Runtime: 19 | 20 | **Description** 21 | 22 | (Please include any relevant CVE advisory links) 23 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 12 | 13 | ## Which problem is this PR solving? 14 | 15 | - 16 | 17 | ## Short description of the changes 18 | 19 | - 20 | 21 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | # .github/release.yml 2 | 3 | changelog: 4 | exclude: 5 | labels: 6 | - no-changelog 7 | categories: 8 | - title: 💥 Breaking Changes 💥 9 | labels: 10 | - "version: bump major" 11 | - breaking-change 12 | - title: 💡 Enhancements 13 | labels: 14 | - "type: enhancement" 15 | - title: 🐛 Fixes 16 | labels: 17 | - "type: bug" 18 | - title: 🛠 Maintenance 19 | labels: 20 | - "type: maintenance" 21 | - title: 🤷 Other Changes 22 | labels: 23 | - "*" -------------------------------------------------------------------------------- /.github/workflows/add-to-project-v2.yml: -------------------------------------------------------------------------------- 1 | name: Add to project 2 | on: 3 | issues: 4 | types: [opened] 5 | pull_request_target: 6 | types: [opened] 7 | jobs: 8 | add-to-project: 9 | runs-on: ubuntu-latest 10 | name: Add issues and PRs to project 11 | steps: 12 | - uses: actions/add-to-project@main 13 | with: 14 | project-url: https://github.com/orgs/honeycombio/projects/27 15 | github-token: ${{ secrets.GHPROJECTS_TOKEN }} 16 | -------------------------------------------------------------------------------- /.github/workflows/apply-labels.yml: -------------------------------------------------------------------------------- 1 | name: Apply project labels 2 | on: [issues, pull_request_target, label] 3 | jobs: 4 | apply-labels: 5 | runs-on: ubuntu-latest 6 | name: Apply common project labels 7 | steps: 8 | - uses: honeycombio/oss-management-actions/labels@v1 9 | with: 10 | github-token: ${{ secrets.GITHUB_TOKEN }} 11 | -------------------------------------------------------------------------------- /.github/workflows/publish-ghcr.yml: -------------------------------------------------------------------------------- 1 | name: Publish to GHCR 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | build-and-publish-docker-image: 11 | runs-on: ubuntu-22.04 12 | permissions: 13 | contents: read 14 | packages: write 15 | strategy: 16 | matrix: 17 | service: [frontend, message, year, name] 18 | steps: 19 | - uses: actions/checkout@v1 20 | 21 | - name: Log in to the Container registry 22 | uses: docker/login-action@v2.1.0 23 | with: 24 | registry: ghcr.io 25 | username: ${{ github.repository_owner }} 26 | password: ${{ secrets.GITHUB_TOKEN }} 27 | 28 | - name: Set up QEMU 29 | uses: docker/setup-qemu-action@v2 30 | 31 | - name: Set up Docker Buildx 32 | uses: docker/setup-buildx-action@v2.5.0 33 | 34 | - name: Extract metadata (tags, labels) for Docker 35 | id: meta 36 | uses: docker/metadata-action@v4.4.0 37 | with: 38 | images: | 39 | ghcr.io/${{ github.repository }}/egs-${{ matrix.service }}-go 40 | 41 | - name: Build and push 42 | uses: docker/build-push-action@v4.0.0 43 | with: 44 | context: "{{ defaultContext }}:go-uninstrumented" 45 | file: ${{ matrix.service }}-service/Dockerfile 46 | tags: ghcr.io/${{ github.repository }}/egs-${{ matrix.service }}-go:dev 47 | push: true 48 | labels: ${{ steps.meta.outputs.labels }} 49 | platforms: linux/amd64,linux/arm64 50 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | 6 | jobs: 7 | stale: 8 | name: 'Close stale issues and PRs' 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | 14 | steps: 15 | - uses: actions/stale@v4 16 | with: 17 | start-date: '2021-09-01T00:00:00Z' 18 | stale-issue-message: 'Marking this issue as stale because it has been open 14 days with no activity. Please add a comment if this is still an ongoing issue; otherwise this issue will be automatically closed in 7 days.' 19 | stale-pr-message: 'Marking this PR as stale because it has been open 30 days with no activity. Please add a comment if this PR is still relevant; otherwise this PR will be automatically closed in 7 days.' 20 | close-issue-message: 'Closing this issue due to inactivity. Please see our [Honeycomb OSS Lifecyle and Practices](https://github.com/honeycombio/home/blob/main/honeycomb-oss-lifecycle-and-practices.md).' 21 | close-pr-message: 'Closing this PR due to inactivity. Please see our [Honeycomb OSS Lifecyle and Practices](https://github.com/honeycombio/home/blob/main/honeycomb-oss-lifecycle-and-practices.md).' 22 | days-before-issue-stale: 14 23 | days-before-pr-stale: 30 24 | days-before-issue-close: 7 25 | days-before-pr-close: 7 26 | any-of-labels: 'status: info needed,status: revision needed' 27 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | This project has adopted the Honeycomb User Community Code of Conduct to clarify expected behavior in our community. 4 | 5 | https://www.honeycomb.io/honeycomb-user-community-code-of-conduct/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | Please see our [general guide for OSS lifecycle and practices.](https://github.com/honeycombio/home/blob/main/honeycomb-oss-lifecycle-and-practices.md) 4 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-Present Honeycomb, Hound Technology, Inc. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /OSSMETADATA: -------------------------------------------------------------------------------- 1 | osslifecycle=active 2 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | This security policy applies to public projects under the [honeycombio organization][gh-organization] on GitHub. 4 | For security reports involving the services provided at `(ui|ui-eu|api|api-eu).honeycomb.io`, refer to the [Honeycomb Bug Bounty Program][bugbounty] for scope, expectations, and reporting procedures. 5 | 6 | ## Security/Bugfix Versions 7 | 8 | Security and bug fixes are generally provided only for the last minor version. 9 | Fixes are released either as part of the next minor version or as an on-demand patch version. 10 | 11 | Security fixes are given priority and might be enough to cause a new version to be released. 12 | 13 | ## Reporting a Vulnerability 14 | 15 | We encourage responsible disclosure of security vulnerabilities. 16 | If you find something suspicious, we encourage and appreciate your report! 17 | 18 | ### Ways to report 19 | 20 | In order for the vulnerability reports to reach maintainers as soon as possible, the preferred way is to use the "Report a vulnerability" button under the "Security" tab of the associated GitHub project. 21 | This creates a private communication channel between the reporter and the maintainers. 22 | 23 | If you are absolutely unable to or have strong reasons not to use GitHub's vulnerability reporting workflow, please reach out to the Honeycomb security team at [security@honeycomb.io](mailto:security@honeycomb.io). 24 | 25 | [gh-organization]: https://github.com/honeycombio 26 | [bugbounty]: https://www.honeycomb.io/bugbountyprogram 27 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # How to Get Help 2 | 3 | This project uses GitHub issues to track bugs, feature requests, and questions about using the project. Please search for existing issues before filing a new one. 4 | -------------------------------------------------------------------------------- /docker-compose.dotnet.yml: -------------------------------------------------------------------------------- 1 | version: "2.4" 2 | 3 | # noinspection ComposeUnknownKeys 4 | x-common-env: &common-env 5 | HONEYCOMB_API_KEY: 6 | HONEYCOMB_DATASET: 7 | HONEYCOMB_API: 8 | OTEL_EXPORTER_OTLP_ENDPOINT: 9 | OTEL_EXPORTER_OTLP_HEADERS: 10 | OTEL_RESOURCE_ATTRIBUTES: app.running-in=docker 11 | Honeycomb__ApiKey: ${HONEYCOMB_API_KEY} 12 | Honeycomb__Dataset: ${HONEYCOMB_DATASET} 13 | Honeycomb__Endpoint: ${HONEYCOMB_API} 14 | Otlp__ApiKey: ${HONEYCOMB_API_KEY} 15 | Otlp__Dataset: ${HONEYCOMB_DATASET} 16 | Otlp__Endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT} 17 | MESSAGE_ENDPOINT: message-service:9000 18 | NAME_ENDPOINT: name-service:8000 19 | YEAR_ENDPOINT: year-service:6001 20 | REDIS_URL: redis 21 | 22 | services: 23 | frontend-dotnet: 24 | container_name: frontend-service 25 | build: ./dotnet/frontend 26 | image: hnyexample/frontend-dotnet 27 | environment: 28 | <<: *common-env 29 | ports: 30 | - 7777:7777 31 | 32 | message-dotnet: 33 | container_name: message-service 34 | build: ./dotnet/message-service 35 | image: hnyexample/message-dotnet 36 | environment: 37 | <<: *common-env 38 | ports: 39 | - 9000:9000 40 | 41 | name-dotnet: 42 | container_name: name-service 43 | build: ./dotnet/name-service 44 | image: hnyexample/name-dotnet 45 | environment: 46 | <<: *common-env 47 | ports: 48 | - 8000:8000 49 | 50 | year-dotnet: 51 | container_name: year-service 52 | build: ./dotnet/year-service 53 | image: hnyexample/year-dotnet 54 | environment: 55 | <<: *common-env 56 | ports: 57 | - 6001:6001 58 | -------------------------------------------------------------------------------- /docker-compose.elixir.yml: -------------------------------------------------------------------------------- 1 | version: "2.4" 2 | 3 | # noinspection ComposeUnknownKeys 4 | x-common-env: &common-env 5 | HONEYCOMB_API_KEY: 6 | HONEYCOMB_DATASET: 7 | HONEYCOMB_API: 8 | OTEL_EXPORTER_OTLP_ENDPOINT: 9 | OTEL_EXPORTER_OTLP_HEADERS: 10 | OTEL_RESOURCE_ATTRIBUTES: app.running-in=docker 11 | MESSAGE_ENDPOINT: message-service:9000 12 | NAME_ENDPOINT: name-service:8000 13 | YEAR_ENDPOINT: year-service:6001 14 | REDIS_URL: redis 15 | OTEL_COLLECTOR_HOST: collector 16 | 17 | services: 18 | frontend-elixir: 19 | container_name: frontend-service 20 | build: ./elixir/frontend 21 | image: hnyexample/frontend-elixir 22 | environment: 23 | <<: *common-env 24 | OTEL_SERVICE_NAME: frontend-elixir 25 | ports: 26 | - 7777:7777 27 | 28 | message-elixir: 29 | container_name: message-service 30 | build: ./elixir/message 31 | image: hnyexample/message-elixir 32 | environment: 33 | <<: *common-env 34 | SERVICE_NAME: message-elixir 35 | ports: 36 | - 9000:9000 37 | 38 | name-elixir: 39 | container_name: name-service 40 | build: ./elixir/name 41 | image: hnyexample/name-elixir 42 | environment: 43 | <<: *common-env 44 | SERVICE_NAME: name-elixir 45 | ports: 46 | - 8000:8000 47 | 48 | year-elixir: 49 | container_name: year-service 50 | build: ./elixir/year 51 | image: hnyexample/year-elixir 52 | environment: 53 | <<: *common-env 54 | SERVICE_NAME: year-elixir 55 | ports: 56 | - 6001:6001 57 | -------------------------------------------------------------------------------- /docker-compose.java.yml: -------------------------------------------------------------------------------- 1 | version: "2.4" 2 | 3 | # noinspection ComposeUnknownKeys 4 | x-common-env: &common-env 5 | HONEYCOMB_API_KEY: 6 | HONEYCOMB_DATASET: 7 | HONEYCOMB_API: 8 | OTEL_EXPORTER_OTLP_ENDPOINT: 9 | OTEL_LOGS_EXPORTER: otlp 10 | OTEL_EXPORTER_OTLP_HEADERS: 11 | OTEL_RESOURCE_ATTRIBUTES: app.running-in=docker 12 | MESSAGE_ENDPOINT: message-service:9000 13 | NAME_ENDPOINT: name-service:8000 14 | YEAR_ENDPOINT: year-service:6001 15 | REDIS_URL: redis 16 | 17 | services: 18 | frontend-java: 19 | container_name: frontend-service 20 | build: ./java/frontend 21 | image: hnyexample/frontend-java 22 | environment: 23 | <<: *common-env 24 | OTEL_SERVICE_NAME: frontend-java 25 | ports: 26 | - 7777:7777 27 | 28 | message-java: 29 | container_name: message-service 30 | build: ./java/message-service 31 | image: hnyexample/message-java 32 | environment: 33 | <<: *common-env 34 | SERVICE_NAME: message-java 35 | ports: 36 | - 9000:9000 37 | 38 | name-java: 39 | container_name: name-service 40 | build: ./java/name-service 41 | image: hnyexample/name-java 42 | environment: 43 | <<: *common-env 44 | SERVICE_NAME: name-java 45 | ports: 46 | - 8000:8000 47 | 48 | year-java: 49 | container_name: year-service 50 | build: ./java/year-service 51 | image: hnyexample/year-java 52 | environment: 53 | <<: *common-env 54 | OTEL_SERVICE_NAME: year-java 55 | ports: 56 | - 6001:6001 57 | -------------------------------------------------------------------------------- /docker-compose.node.yml: -------------------------------------------------------------------------------- 1 | version: "2.4" 2 | 3 | x-common-env: &common-env 4 | HONEYCOMB_API_KEY: ${HONEYCOMB_API_KEY} 5 | OTEL_EXPORTER_OTLP_ENDPOINT: api.honeycomb.io 6 | OTEL_EXPORTER_OTLP_HEADERS: "x-honeycomb-team=${HONEYCOMB_API_KEY}" 7 | OTEL_RESOURCE_ATTRIBUTES: app.running-in=docker 8 | MESSAGE_ENDPOINT: http://message-service:9000 9 | NAME_ENDPOINT: http://name-service:8000 10 | YEAR_ENDPOINT: http://year-service:6001 11 | REDIS_URL: redis 12 | 13 | services: 14 | frontend-node: 15 | container_name: frontend-service 16 | build: ./node/frontend-service 17 | environment: 18 | <<: *common-env 19 | SERVICE_NAME: node-frontend-service 20 | ports: 21 | - 7777:7777 22 | 23 | message-node: 24 | container_name: message-service 25 | build: ./node/message-service 26 | environment: 27 | <<: *common-env 28 | SERVICE_NAME: node-message-service 29 | ports: 30 | - 9000:9000 31 | 32 | name-node: 33 | container_name: name-service 34 | build: ./node/name-service 35 | environment: 36 | <<: *common-env 37 | SERVICE_NAME: node-name-service 38 | ports: 39 | - 8000:8000 40 | 41 | year-node: 42 | container_name: year-service 43 | build: ./node/year-service 44 | environment: 45 | <<: *common-env 46 | OTEL_SERVICE_NAME: node-year-service 47 | ports: 48 | - 6001:6001 49 | -------------------------------------------------------------------------------- /docker-compose.python.yml: -------------------------------------------------------------------------------- 1 | version: "2.4" 2 | 3 | # noinspection ComposeUnknownKeys 4 | x-common-env: &common-env 5 | HONEYCOMB_API_KEY: 6 | HONEYCOMB_DATASET: 7 | HONEYCOMB_API: 8 | OTEL_EXPORTER_OTLP_ENDPOINT: 9 | OTEL_EXPORTER_OTLP_HEADERS: 10 | OTEL_RESOURCE_ATTRIBUTES: app.running-in=docker 11 | MESSAGE_ENDPOINT: http://message-service:9000 12 | NAME_ENDPOINT: http://name-service:8000 13 | YEAR_ENDPOINT: http://year-service:6001 14 | 15 | services: 16 | frontend-python: 17 | container_name: frontend-service 18 | build: ./python/frontend 19 | image: hnyexample/frontend-python 20 | environment: 21 | <<: *common-env 22 | OTEL_SERVICE_NAME: frontend-python 23 | ports: 24 | - 7777:7777 25 | 26 | message-python: 27 | container_name: message-service 28 | build: ./python/message-service 29 | image: hnyexample/message-python 30 | environment: 31 | <<: *common-env 32 | OTEL_SERVICE_NAME: message-python 33 | ports: 34 | - 9000:9000 35 | 36 | name-python: 37 | container_name: name-service 38 | build: ./python/name-service 39 | image: hnyexample/name-python 40 | environment: 41 | <<: *common-env 42 | OTEL_SERVICE_NAME: name-python 43 | ports: 44 | - 8000:8000 45 | 46 | year-python: 47 | container_name: year-service 48 | build: ./python/year-service 49 | image: hnyexample/year-python 50 | environment: 51 | <<: *common-env 52 | OTEL_SERVICE_NAME: year-python 53 | ports: 54 | - 6001:6001 55 | 56 | -------------------------------------------------------------------------------- /docker-compose.web.yml: -------------------------------------------------------------------------------- 1 | version: "2.4" 2 | 3 | x-common-env: &common-env 4 | OTEL_RESOURCE_ATTRIBUTES: app.running-in=docker 5 | REDIS_URL: redis 6 | 7 | services: 8 | web: 9 | container_name: web-app 10 | build: ./web 11 | environment: 12 | <<: *common-env 13 | SERVICE_NAME: web-app 14 | ports: 15 | - 8080:8080 16 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.4" 2 | 3 | services: 4 | collector: 5 | image: otel/opentelemetry-collector-contrib:0.81.0 6 | command: ["--config=/etc/otel-collector-config.yaml"] 7 | environment: 8 | - HONEYCOMB_API_KEY 9 | - HONEYCOMB_DATASET 10 | volumes: 11 | - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml 12 | ports: 13 | - "127.0.0.1:55680:55680" 14 | - "127.0.0.1:55681:55681" 15 | redis: 16 | image: redis:latest 17 | ports: 18 | - "127.0.0.1:6379:6379" 19 | -------------------------------------------------------------------------------- /dotnet/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | .vscode 4 | -------------------------------------------------------------------------------- /dotnet/README.md: -------------------------------------------------------------------------------- 1 | ## .NET greeting service(s) 2 | 3 | This is a .NET implementation of the example greeting service; 4 microservices that do some fancy greetin'. 4 | 5 | You'll note that instrumentation is done differently depending on the project: 6 | 7 | * `frontend` is a service built with .NET 6 that uses [Honeycomb.OpenTelemetry](https://www.nuget.org/packages/Honeycomb.OpenTelemetry), our distribution of the .NET OpenTelemetry SDK. Configuration is simpler when you use this package. 8 | * `message-service` is a service built with .NET 5 that uses the Honeycomb.OpenTelemetry distribution. 9 | * `year-service` is a service built as a minimal .NET 6 API that uses the standard OpenTelemetry libraries to configure instrumentation. It's more lines of code. 10 | * `name-service` is a service built with .NET 5 that uses the standard OpenTelemetry libraries. 11 | 12 | The goal here is to demonstrate that you can configure instrumentation in several ways with .NET but still send data to Honeycomb. -------------------------------------------------------------------------------- /dotnet/dotnet.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "frontend", "frontend\frontend.csproj", "{A4B39423-DB5F-4B6A-A05C-A372D383BC9A}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "message-service", "message-service\message-service.csproj", "{BEBB59BB-089B-472F-A303-2E7230C2D0F0}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "name-service", "name-service\name-service.csproj", "{7E3ED8AF-1156-4B1E-81D9-2D4D227ABDA5}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "year-service", "year-service\year-service.csproj", "{928D8767-4DC8-4CCC-AE79-D4D3F943FF48}" 10 | EndProject 11 | Global 12 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 13 | Debug|Any CPU = Debug|Any CPU 14 | Release|Any CPU = Release|Any CPU 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {A4B39423-DB5F-4B6A-A05C-A372D383BC9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {A4B39423-DB5F-4B6A-A05C-A372D383BC9A}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {A4B39423-DB5F-4B6A-A05C-A372D383BC9A}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {A4B39423-DB5F-4B6A-A05C-A372D383BC9A}.Release|Any CPU.Build.0 = Release|Any CPU 21 | {BEBB59BB-089B-472F-A303-2E7230C2D0F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {BEBB59BB-089B-472F-A303-2E7230C2D0F0}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {BEBB59BB-089B-472F-A303-2E7230C2D0F0}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {BEBB59BB-089B-472F-A303-2E7230C2D0F0}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {7E3ED8AF-1156-4B1E-81D9-2D4D227ABDA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {7E3ED8AF-1156-4B1E-81D9-2D4D227ABDA5}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {7E3ED8AF-1156-4B1E-81D9-2D4D227ABDA5}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {7E3ED8AF-1156-4B1E-81D9-2D4D227ABDA5}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {928D8767-4DC8-4CCC-AE79-D4D3F943FF48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {928D8767-4DC8-4CCC-AE79-D4D3F943FF48}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {928D8767-4DC8-4CCC-AE79-D4D3F943FF48}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {928D8767-4DC8-4CCC-AE79-D4D3F943FF48}.Release|Any CPU.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /dotnet/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | # https://hub.docker.com/_/microsoft-dotnet 2 | FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build 3 | WORKDIR /source 4 | 5 | # copy csproj and restore as distinct layers 6 | COPY *.cs ./ 7 | COPY *.csproj ./ 8 | COPY *.json ./ 9 | COPY ./Properties ./Properties 10 | COPY ./Controllers ./Controllers 11 | RUN dotnet restore 12 | 13 | # copy everything else and build app 14 | RUN dotnet publish -c release -o /app --no-restore 15 | 16 | # final stage/image 17 | FROM mcr.microsoft.com/dotnet/aspnet:6.0 18 | WORKDIR /app 19 | COPY --from=build /app ./ 20 | 21 | ENV ASPNETCORE_URLS=http://+:7777 22 | ENV ASPNETCORE_ENVIRONMENT="development" 23 | EXPOSE 7777 24 | ENTRYPOINT ["dotnet", "frontend.dll"] 25 | -------------------------------------------------------------------------------- /dotnet/frontend/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTelemetry.Trace; 2 | 3 | var builder = WebApplication.CreateBuilder(args); 4 | 5 | // Add services to the container. 6 | 7 | builder.Services.AddControllers(); 8 | builder.Services.AddHttpClient(); 9 | var honeycombOptions = builder.Configuration.GetHoneycombOptions(); 10 | builder.Services.AddOpenTelemetry().WithTracing(otelBuilder => 11 | { 12 | otelBuilder 13 | .AddHoneycomb(honeycombOptions) 14 | .AddCommonInstrumentations() 15 | .AddAspNetCoreInstrumentationWithBaggage(); 16 | }); 17 | builder.Services.AddSingleton(TracerProvider.Default.GetTracer(honeycombOptions.ServiceName)); 18 | 19 | // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 20 | builder.Services.AddEndpointsApiExplorer(); 21 | builder.Services.AddSwaggerGen(); 22 | 23 | var app = builder.Build(); 24 | 25 | // Configure the HTTP request pipeline. 26 | if (app.Environment.IsDevelopment()) 27 | { 28 | app.UseSwagger(); 29 | app.UseSwaggerUI(); 30 | } 31 | 32 | app.UseHttpsRedirection(); 33 | 34 | app.UseAuthorization(); 35 | 36 | app.MapControllers(); 37 | 38 | app.Run(); 39 | -------------------------------------------------------------------------------- /dotnet/frontend/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:36268", 8 | "sslPort": 44358 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "frontend": { 21 | "commandName": "Project", 22 | "dotnetRunMessages": "true", 23 | "applicationUrl": "http://localhost:7777", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /dotnet/frontend/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /dotnet/frontend/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "Honeycomb": { 11 | "ServiceName": "frontend-dotnet", 12 | "Endpoint": "https://api.honeycomb.io", 13 | "ApiKey": "", 14 | "Dataset": "greetings" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /dotnet/frontend/frontend.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /dotnet/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "6.0", 4 | "rollForward": "latestMajor", 5 | "allowPrerelease": true 6 | } 7 | } -------------------------------------------------------------------------------- /dotnet/message-service/Controllers/MessageController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using OpenTelemetry.Trace; 3 | using System.Threading.Tasks; 4 | 5 | namespace message_service.Controllers 6 | { 7 | using StackExchange.Redis; 8 | 9 | [Route("[controller]")] 10 | [ApiController] 11 | public class MessageController : ControllerBase 12 | { 13 | private readonly Tracer _tracer; 14 | private readonly IConnectionMultiplexer _redisConnection; 15 | 16 | public MessageController(Tracer tracer, IConnectionMultiplexer redisConnection) 17 | { 18 | _tracer = tracer; 19 | _redisConnection = redisConnection; 20 | } 21 | 22 | [HttpGet] 23 | public async Task GetAsync() 24 | { 25 | var message = await DetermineMessage(); 26 | var currentSpan = Tracer.CurrentSpan; 27 | currentSpan.SetAttribute("app.message", message); 28 | return message; 29 | } 30 | 31 | private async Task DetermineMessage() 32 | { 33 | using var span = _tracer.StartActiveSpan("📖 look up message ✨"); 34 | var db = _redisConnection.GetDatabase(); 35 | var message = "generic hello"; 36 | var result = await db.SetRandomMemberAsync("messages"); 37 | if (result.IsNull) 38 | { 39 | span.AddEvent("message was empty from redis, using default"); 40 | } 41 | else 42 | { 43 | message = result.ToString(); 44 | } 45 | span.SetAttribute("app.message", message); 46 | return message; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /dotnet/message-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # https://hub.docker.com/_/microsoft-dotnet 2 | FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build 3 | WORKDIR /source 4 | 5 | # copy csproj and restore as distinct layers 6 | COPY *.cs ./ 7 | COPY *.csproj ./ 8 | COPY *.json ./ 9 | COPY ./Properties ./Properties 10 | COPY ./Controllers ./Controllers 11 | RUN dotnet restore 12 | 13 | # copy everything else and build app 14 | RUN dotnet publish -c release -o /app --no-restore 15 | 16 | # final stage/image 17 | FROM mcr.microsoft.com/dotnet/aspnet:5.0 18 | WORKDIR /app 19 | COPY --from=build /app ./ 20 | 21 | ENV ASPNETCORE_URLS=http://+:9000 22 | ENV ASPNETCORE_ENVIRONMENT="development" 23 | EXPOSE 9000 24 | ENTRYPOINT ["dotnet", "message-service.dll"] 25 | -------------------------------------------------------------------------------- /dotnet/message-service/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace message_service 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); 16 | } 17 | } -------------------------------------------------------------------------------- /dotnet/message-service/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:36268", 8 | "sslPort": 44358 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "message_service": { 21 | "commandName": "Project", 22 | "dotnetRunMessages": "true", 23 | "applicationUrl": "http://localhost:9000", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /dotnet/message-service/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /dotnet/message-service/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "Honeycomb": { 11 | "ServiceName": "message-dotnet", 12 | "Endpoint": "https://api.honeycomb.io", 13 | "ApiKey": "", 14 | "Dataset": "greetings" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /dotnet/message-service/message-service.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | message_service 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /dotnet/name-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # https://hub.docker.com/_/microsoft-dotnet 2 | FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build 3 | WORKDIR /source 4 | 5 | # copy csproj and restore as distinct layers 6 | COPY *.cs ./ 7 | COPY *.csproj ./ 8 | COPY *.json ./ 9 | COPY ./Properties ./Properties 10 | COPY ./Controllers ./Controllers 11 | RUN dotnet restore 12 | 13 | # copy everything else and build app 14 | RUN dotnet publish -c release -o /app --no-restore 15 | 16 | # final stage/image 17 | FROM mcr.microsoft.com/dotnet/aspnet:5.0 18 | WORKDIR /app 19 | COPY --from=build /app ./ 20 | 21 | ENV ASPNETCORE_URLS=http://+:8000 22 | ENV ASPNETCORE_ENVIRONMENT="development" 23 | EXPOSE 8000 24 | ENTRYPOINT ["dotnet", "name-service.dll"] 25 | -------------------------------------------------------------------------------- /dotnet/name-service/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace name_service 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); 16 | } 17 | } -------------------------------------------------------------------------------- /dotnet/name-service/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:36268", 8 | "sslPort": 44358 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "name_service": { 21 | "commandName": "Project", 22 | "dotnetRunMessages": "true", 23 | "applicationUrl": "http://localhost:8000", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /dotnet/name-service/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /dotnet/name-service/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "Otlp": { 11 | "ServiceName": "name-dotnet", 12 | "Endpoint": "https://api.honeycomb.io", 13 | "ApiKey": "", 14 | "Dataset": "greetings" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /dotnet/name-service/name-service.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | name_service 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /dotnet/year-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # https://hub.docker.com/_/microsoft-dotnet 2 | FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build 3 | WORKDIR /source 4 | 5 | # copy csproj and restore as distinct layers 6 | COPY *.cs ./ 7 | COPY *.csproj ./ 8 | COPY *.json ./ 9 | COPY ./Properties ./Properties 10 | RUN dotnet restore 11 | 12 | # copy everything else and build app 13 | RUN dotnet publish -c release -o /app --no-restore 14 | 15 | # final stage/image 16 | FROM mcr.microsoft.com/dotnet/aspnet:6.0 17 | WORKDIR /app 18 | COPY --from=build /app ./ 19 | 20 | ENV ASPNETCORE_URLS=http://+:6001 21 | ENV ASPNETCORE_ENVIRONMENT="development" 22 | EXPOSE 6001 23 | ENTRYPOINT ["dotnet", "year-service.dll"] 24 | -------------------------------------------------------------------------------- /dotnet/year-service/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTelemetry.Resources; 2 | using OpenTelemetry.Trace; 3 | using OpenTelemetry.Logs; 4 | using OpenTelemetry.Exporter; 5 | 6 | var builder = WebApplication.CreateBuilder(args); 7 | 8 | const string telemetrySourceName = "honeycomb.examples.year-service-dotnet"; 9 | 10 | var resourceBuilder = ResourceBuilder.CreateDefault() 11 | .AddService(builder.Configuration.GetValue("Otlp:ServiceName")) 12 | .AddEnvironmentVariableDetector(); 13 | 14 | var configureOtlpExporter = (OtlpExporterOptions options) => 15 | { 16 | options.Endpoint = new Uri(builder.Configuration.GetValue("Otlp:Endpoint")); 17 | var apiKey = builder.Configuration.GetValue("Otlp:ApiKey"); 18 | var dataset = builder.Configuration.GetValue("Otlp:Dataset"); 19 | options.Headers = $"x-honeycomb-team={apiKey},x-honeycomb-dataset={dataset}"; 20 | }; 21 | 22 | builder.Services.AddOpenTelemetry() 23 | .WithTracing(options => options 24 | .SetResourceBuilder(resourceBuilder) 25 | .AddSource(telemetrySourceName) 26 | .AddAspNetCoreInstrumentation() 27 | .AddHttpClientInstrumentation() 28 | .AddOtlpExporter(configureOtlpExporter)); 29 | 30 | builder.Logging.AddOpenTelemetry(options => options 31 | .SetResourceBuilder(resourceBuilder) 32 | .AddOtlpExporter(configureOtlpExporter) 33 | ); 34 | 35 | Tracer tracer = TracerProvider.Default.GetTracer(telemetrySourceName); 36 | var app = builder.Build(); 37 | int[] years = { 2015, 2016, 2017, 2018, 2019, 2020 }; 38 | 39 | app.MapGet("/year", () => 40 | { 41 | using var span = tracer.StartActiveSpan("🗓 get-a-year ✨"); 42 | span.SetAttribute("banana", 1); 43 | var rng = new Random(); 44 | var i = rng.Next(years.Length - 1); 45 | var year = years[i]; 46 | app.Logger.LogInformation($"Selected year: ${year}"); 47 | return year; 48 | }); 49 | 50 | app.Run(); 51 | -------------------------------------------------------------------------------- /dotnet/year-service/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:36268", 8 | "sslPort": 44358 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "year_service": { 21 | "commandName": "Project", 22 | "dotnetRunMessages": "true", 23 | "launchUrl": "api/year", 24 | "applicationUrl": "http://localhost:6001", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /dotnet/year-service/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /dotnet/year-service/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "Otlp": { 11 | "ServiceName": "year-dotnet", 12 | "Endpoint": "https://api.honeycomb.io", 13 | "ApiKey": "", 14 | "Dataset": "greetings" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /dotnet/year-service/year-service.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /elixir/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # When opening a file, EditorConfig plugins look for a file named .editorconfig 4 | # in the directory of the opened file and in every parent directory. 5 | # A search for .editorconfig files will stop if the root filepath is reached 6 | # or an EditorConfig file with root=true is found. 7 | 8 | # EditorConfig files are read top to bottom and the most recent rules found 9 | # take precedence. Properties from matching EditorConfig sections are applied 10 | # in the order they were read, so properties in closer files take precedence. 11 | 12 | # https://gist.github.com/eksperimental/6a43b9a0ab8d718f5886e7025062fcbd 13 | 14 | # Elixir files 15 | [*.eex, *.exs] 16 | 17 | indent_size = 2 18 | end_of_line = lf 19 | charset = utf-8 20 | trim_trailing_whitespace = true 21 | insert_final_newline = true 22 | 23 | [Makefile] 24 | indent_style = tab -------------------------------------------------------------------------------- /elixir/.tool-versions: -------------------------------------------------------------------------------- 1 | elixir 1.14.3 2 | erlang 25.3 3 | -------------------------------------------------------------------------------- /elixir/README.md: -------------------------------------------------------------------------------- 1 | # Elixir Greeting Services 2 | 3 | ## Services 4 | - Frontend: Service that calls the message, name and year services to create a greeting message served at `localhost:7777/greeting`. 5 | - Message: Service that chooses a random greeting message. 6 | - Name: Service that chooses a random name. 7 | - Year: Service that returns a random year. 8 | 9 | ## How to run without Tilt/Docker 10 | 11 | See top-level README.md for instructions on how to run services with Tilt and Docker (easiest). 12 | 13 | ### Install Elixir 14 | If using option 1 or 3 below, make sure you have Elixir installed. The preferred way to install Elixir is with [asdf](https://asdf-vm.com/guide/getting-started.html#_1-install-dependencies). 15 | 16 | Once you have asdf set up, run the following to install Elixir & Erlang. Check the version needed in the `.tool-versions` file in the root of this repo. 17 | 18 | ```shell 19 | asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git 20 | ``` 21 | 22 | ```shell 23 | asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git 24 | ``` 25 | 26 | ```shell 27 | asdf install erlang 28 | asdf install elixir 29 | ``` 30 | 31 | ### Run each service 32 | 33 | Install dependencies in the service directory 34 | 35 | ```shell 36 | mix local.hex --force && mix local.rebar --force && mix deps.get && mix deps.compile 37 | ``` 38 | 39 | In each service directory, run the mix command that starts the service. 40 | 41 | Frontend: 42 | 43 | ```shell 44 | mix phx.server 45 | ``` 46 | 47 | Message/Name/Year: 48 | 49 | ```shell 50 | mix run --no-halt 51 | ``` 52 | 53 | ## See it in action 54 | 55 | `curl localhost:7777/greeting` for greeting 56 | 57 | `curl localhost:9000/message` for message only 58 | 59 | `curl localhost:8000/name` for name only 60 | 61 | `curl localhost:6001` for year only 62 | 63 | ## Troubleshooting 64 | 65 | ### Port in use error 66 | 67 | If an error comes up about a port in use, either tilt or docker might still be running in the background. 68 | 69 | To shut down docker services run `docker-compose down` 70 | 71 | To shut down tilt services run `tilt down` 72 | -------------------------------------------------------------------------------- /elixir/frontend/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:phoenix], 3 | inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /elixir/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | frontend-*.tar 24 | 25 | # Since we are building assets from assets/, 26 | # we ignore priv/static. You may want to comment 27 | # this depending on your deployment strategy. 28 | /priv/static/ 29 | -------------------------------------------------------------------------------- /elixir/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM elixir:1.14 2 | WORKDIR /app 3 | COPY mix.* /app 4 | COPY lib /app/lib 5 | COPY config /app/config 6 | RUN mix local.hex --force 7 | RUN mix local.rebar --force 8 | RUN mix deps.get 9 | RUN mix deps.compile 10 | 11 | EXPOSE 7777 12 | CMD mix phx.server 13 | -------------------------------------------------------------------------------- /elixir/frontend/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | 7 | # General application configuration 8 | import Config 9 | 10 | # Configures the endpoint 11 | config :frontend, FrontendWeb.Endpoint, 12 | url: [host: "localhost"], 13 | secret_key_base: "28gPO3Atbcv0ZX9ATXD6/prb3MOwl6DHGGbHWP8jEIzP4xV4/GPftCS+BtGqgO7n", 14 | render_errors: [view: FrontendWeb.ErrorView, accepts: ~w(json), layout: false], 15 | pubsub_server: Frontend.PubSub, 16 | live_view: [signing_salt: "RCSHpWzo"] 17 | 18 | # Configures Elixir's Logger 19 | config :logger, :console, 20 | format: "$time $metadata[$level] $message\n", 21 | metadata: [:request_id] 22 | 23 | # Use Jason for JSON parsing in Phoenix 24 | config :phoenix, :json_library, Jason 25 | 26 | # Configures OpenTelemetry 27 | config :opentelemetry, :resource, service: [name: "frontend-elixir"] 28 | config :opentelemetry, :propagators, :otel_propagator_http_w3c 29 | 30 | config :opentelemetry, :processors, 31 | otel_batch_processor: %{ 32 | exporter: { 33 | :opentelemetry_exporter, 34 | %{ 35 | endpoints: [ 36 | { 37 | :http, 38 | System.get_env("OTEL_COLLECTOR_HOST", "localhost"), 39 | System.get_env("OTEL_COLLECTOR_PORT", "55681") |> String.to_integer(), 40 | [] 41 | } 42 | ] 43 | } 44 | } 45 | } 46 | 47 | # Import environment specific config. This must remain at the bottom 48 | # of this file so it overrides the configuration defined above. 49 | import_config "#{Mix.env()}.exs" 50 | -------------------------------------------------------------------------------- /elixir/frontend/config/dev.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # For development, we disable any cache and enable 4 | # debugging and code reloading. 5 | # 6 | # The watchers configuration can be used to run external 7 | # watchers to your application. For example, we use it 8 | # with webpack to recompile .js and .css sources. 9 | config :frontend, FrontendWeb.Endpoint, 10 | http: [port: 7777], 11 | debug_errors: true, 12 | code_reloader: true, 13 | check_origin: false, 14 | watchers: [] 15 | 16 | # ## SSL Support 17 | # 18 | # In order to use HTTPS in development, a self-signed 19 | # certificate can be generated by running the following 20 | # Mix task: 21 | # 22 | # mix phx.gen.cert 23 | # 24 | # Note that this task requires Erlang/OTP 20 or later. 25 | # Run `mix help phx.gen.cert` for more information. 26 | # 27 | # The `http:` config above can be replaced with: 28 | # 29 | # https: [ 30 | # port: 4001, 31 | # cipher_suite: :strong, 32 | # keyfile: "priv/cert/selfsigned_key.pem", 33 | # certfile: "priv/cert/selfsigned.pem" 34 | # ], 35 | # 36 | # If desired, both `http:` and `https:` keys can be 37 | # configured to run both http and https servers on 38 | # different ports. 39 | 40 | # Do not include metadata nor timestamps in development logs 41 | config :logger, :console, format: "[$level] $message\n" 42 | 43 | # Set a higher stacktrace during development. Avoid configuring such 44 | # in production as building large stacktraces may be expensive. 45 | config :phoenix, :stacktrace_depth, 20 46 | 47 | # Initialize plugs at runtime for faster development compilation 48 | config :phoenix, :plug_init_mode, :runtime 49 | -------------------------------------------------------------------------------- /elixir/frontend/config/prod.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # For production, don't forget to configure the url host 4 | # to something meaningful, Phoenix uses this information 5 | # when generating URLs. 6 | # 7 | # Note we also include the path to a cache manifest 8 | # containing the digested version of static files. This 9 | # manifest is generated by the `mix phx.digest` task, 10 | # which you should run after static files are built and 11 | # before starting your production server. 12 | config :frontend, FrontendWeb.Endpoint, 13 | url: [host: "example.com", port: 80], 14 | cache_static_manifest: "priv/static/cache_manifest.json" 15 | 16 | # Do not print debug messages in production 17 | config :logger, level: :info 18 | 19 | # ## SSL Support 20 | # 21 | # To get SSL working, you will need to add the `https` key 22 | # to the previous section and set your `:url` port to 443: 23 | # 24 | # config :frontend, FrontendWeb.Endpoint, 25 | # ... 26 | # url: [host: "example.com", port: 443], 27 | # https: [ 28 | # port: 443, 29 | # cipher_suite: :strong, 30 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), 31 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH"), 32 | # transport_options: [socket_opts: [:inet6]] 33 | # ] 34 | # 35 | # The `cipher_suite` is set to `:strong` to support only the 36 | # latest and more secure SSL ciphers. This means old browsers 37 | # and clients may not be supported. You can set it to 38 | # `:compatible` for wider support. 39 | # 40 | # `:keyfile` and `:certfile` expect an absolute path to the key 41 | # and cert in disk or a relative path inside priv, for example 42 | # "priv/ssl/server.key". For all supported SSL configuration 43 | # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 44 | # 45 | # We also recommend setting `force_ssl` in your endpoint, ensuring 46 | # no data is ever sent via http, always redirecting to https: 47 | # 48 | # config :frontend, FrontendWeb.Endpoint, 49 | # force_ssl: [hsts: true] 50 | # 51 | # Check `Plug.SSL` for all available options in `force_ssl`. 52 | 53 | # Finally import the config/prod.secret.exs which loads secrets 54 | # and configuration from environment variables. 55 | import_config "prod.secret.exs" 56 | -------------------------------------------------------------------------------- /elixir/frontend/config/prod.secret.exs: -------------------------------------------------------------------------------- 1 | # In this file, we load production configuration and secrets 2 | # from environment variables. You can also hardcode secrets, 3 | # although such is generally not recommended and you have to 4 | # remember to add this file to your .gitignore. 5 | import Config 6 | 7 | secret_key_base = 8 | System.get_env("SECRET_KEY_BASE") || 9 | raise """ 10 | environment variable SECRET_KEY_BASE is missing. 11 | You can generate one by calling: mix phx.gen.secret 12 | """ 13 | 14 | config :frontend, FrontendWeb.Endpoint, 15 | http: [ 16 | port: String.to_integer(System.get_env("PORT") || "7777"), 17 | transport_options: [socket_opts: [:inet6]] 18 | ], 19 | secret_key_base: secret_key_base 20 | 21 | # ## Using releases (Elixir v1.9+) 22 | # 23 | # If you are doing OTP releases, you need to instruct Phoenix 24 | # to start each relevant endpoint: 25 | # 26 | # config :frontend, FrontendWeb.Endpoint, server: true 27 | # 28 | # Then you can assemble a release by calling `mix release`. 29 | # See `mix help release` for more information. 30 | -------------------------------------------------------------------------------- /elixir/frontend/config/test.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # We don't run a server during test. If one is required, 4 | # you can enable the server option below. 5 | config :frontend, FrontendWeb.Endpoint, 6 | http: [port: 4002], 7 | server: false 8 | 9 | # Print only warnings and errors during test 10 | config :logger, level: :warn 11 | -------------------------------------------------------------------------------- /elixir/frontend/lib/frontend.ex: -------------------------------------------------------------------------------- 1 | defmodule Frontend do 2 | require OpenTelemetry.Tracer, as: Tracer 3 | 4 | def name do 5 | Tracer.with_span "✨ call /name ✨" do 6 | endpoint = System.get_env("NAME_ENDPOINT", "http://localhost:8000") 7 | headers = :otel_propagator_text_map.inject([]) 8 | HTTPoison.get!("#{endpoint}/name", headers).body 9 | end 10 | end 11 | 12 | def message do 13 | Tracer.with_span "✨ call /message ✨" do 14 | endpoint = System.get_env("MESSAGE_ENDPOINT", "http://localhost:9000") 15 | headers = :otel_propagator_text_map.inject([]) 16 | HTTPoison.get!("#{endpoint}/message", headers).body 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /elixir/frontend/lib/frontend/application.ex: -------------------------------------------------------------------------------- 1 | defmodule Frontend.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | def start(_type, _args) do 9 | children = [ 10 | # Start the Telemetry supervisor 11 | FrontendWeb.Telemetry, 12 | # Start the PubSub system 13 | {Phoenix.PubSub, name: Frontend.PubSub}, 14 | # Start the Endpoint (http/https) 15 | FrontendWeb.Endpoint 16 | # Start a worker by calling: Frontend.Worker.start_link(arg) 17 | # {Frontend.Worker, arg} 18 | ] 19 | 20 | OpentelemetryPhoenix.setup() 21 | 22 | # See https://hexdocs.pm/elixir/Supervisor.html 23 | # for other strategies and supported options 24 | opts = [strategy: :one_for_one, name: Frontend.Supervisor] 25 | Supervisor.start_link(children, opts) 26 | end 27 | 28 | # Tell Phoenix to update the endpoint configuration 29 | # whenever the application is updated. 30 | def config_change(changed, _new, removed) do 31 | FrontendWeb.Endpoint.config_change(changed, removed) 32 | :ok 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /elixir/frontend/lib/frontend_web.ex: -------------------------------------------------------------------------------- 1 | defmodule FrontendWeb do 2 | @moduledoc """ 3 | The entrypoint for defining your web interface, such 4 | as controllers, views, channels and so on. 5 | 6 | This can be used in your application as: 7 | 8 | use FrontendWeb, :controller 9 | use FrontendWeb, :view 10 | 11 | The definitions below will be executed for every view, 12 | controller, etc, so keep them short and clean, focused 13 | on imports, uses and aliases. 14 | 15 | Do NOT define functions inside the quoted expressions 16 | below. Instead, define any helper function in modules 17 | and import those modules here. 18 | """ 19 | 20 | def controller do 21 | quote do 22 | use Phoenix.Controller, namespace: FrontendWeb 23 | 24 | import Plug.Conn 25 | alias FrontendWeb.Router.Helpers, as: Routes 26 | end 27 | end 28 | 29 | def view do 30 | quote do 31 | use Phoenix.View, 32 | root: "lib/frontend_web/templates", 33 | namespace: FrontendWeb 34 | 35 | # Import convenience functions from controllers 36 | import Phoenix.Controller, 37 | only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1] 38 | 39 | # Include shared imports and aliases for views 40 | unquote(view_helpers()) 41 | end 42 | end 43 | 44 | def router do 45 | quote do 46 | use Phoenix.Router 47 | 48 | import Plug.Conn 49 | import Phoenix.Controller 50 | end 51 | end 52 | 53 | def channel do 54 | quote do 55 | use Phoenix.Channel 56 | end 57 | end 58 | 59 | defp view_helpers do 60 | quote do 61 | # Import basic rendering functionality (render, render_layout, etc) 62 | import Phoenix.View 63 | 64 | import FrontendWeb.ErrorHelpers 65 | alias FrontendWeb.Router.Helpers, as: Routes 66 | end 67 | end 68 | 69 | @doc """ 70 | When used, dispatch to the appropriate controller/view/etc. 71 | """ 72 | defmacro __using__(which) when is_atom(which) do 73 | apply(__MODULE__, which, []) 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /elixir/frontend/lib/frontend_web/controllers/greetings_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule FrontendWeb.GreetingsController do 2 | use FrontendWeb, :controller 3 | 4 | require OpenTelemetry.Tracer, as: Tracer 5 | 6 | def index(conn, _params) do 7 | name = Frontend.name() 8 | message = Frontend.message() 9 | Tracer.with_span "🎨 render message ✨" do 10 | text(conn, "Hello #{name}, #{message}") 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /elixir/frontend/lib/frontend_web/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule FrontendWeb.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :frontend 3 | 4 | plug Plug.RequestId 5 | plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] 6 | plug FrontendWeb.Router 7 | end 8 | -------------------------------------------------------------------------------- /elixir/frontend/lib/frontend_web/router.ex: -------------------------------------------------------------------------------- 1 | defmodule FrontendWeb.Router do 2 | use FrontendWeb, :router 3 | 4 | get "/greeting", FrontendWeb.GreetingsController, :index 5 | end 6 | -------------------------------------------------------------------------------- /elixir/frontend/lib/frontend_web/telemetry.ex: -------------------------------------------------------------------------------- 1 | defmodule FrontendWeb.Telemetry do 2 | use Supervisor 3 | import Telemetry.Metrics 4 | 5 | def start_link(arg) do 6 | Supervisor.start_link(__MODULE__, arg, name: __MODULE__) 7 | end 8 | 9 | @impl true 10 | def init(_arg) do 11 | children = [ 12 | # Telemetry poller will execute the given period measurements 13 | # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics 14 | {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} 15 | # Add reporters as children of your supervision tree. 16 | # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} 17 | ] 18 | 19 | Supervisor.init(children, strategy: :one_for_one) 20 | end 21 | 22 | def metrics do 23 | [ 24 | # Phoenix Metrics 25 | summary("phoenix.endpoint.stop.duration", 26 | unit: {:native, :millisecond} 27 | ), 28 | summary("phoenix.router_dispatch.stop.duration", 29 | tags: [:route], 30 | unit: {:native, :millisecond} 31 | ), 32 | 33 | # VM Metrics 34 | summary("vm.memory.total", unit: {:byte, :kilobyte}), 35 | summary("vm.total_run_queue_lengths.total"), 36 | summary("vm.total_run_queue_lengths.cpu"), 37 | summary("vm.total_run_queue_lengths.io") 38 | ] 39 | end 40 | 41 | defp periodic_measurements do 42 | [ 43 | # A module, function and arguments to be invoked periodically. 44 | # This function must call :telemetry.execute/3 and a metric must be added above. 45 | # {FrontendWeb, :count_users, []} 46 | ] 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /elixir/frontend/lib/frontend_web/views/error_helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule FrontendWeb.ErrorHelpers do 2 | @moduledoc """ 3 | Conveniences for translating and building error messages. 4 | """ 5 | 6 | @doc """ 7 | Translates an error message. 8 | """ 9 | def translate_error({msg, opts}) do 10 | # Because the error messages we show in our forms and APIs 11 | # are defined inside Ecto, we need to translate them dynamically. 12 | Enum.reduce(opts, msg, fn {key, value}, acc -> 13 | String.replace(acc, "%{#{key}}", to_string(value)) 14 | end) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /elixir/frontend/lib/frontend_web/views/error_view.ex: -------------------------------------------------------------------------------- 1 | defmodule FrontendWeb.ErrorView do 2 | use FrontendWeb, :view 3 | 4 | # If you want to customize a particular status code 5 | # for a certain format, you may uncomment below. 6 | # def render("500.json", _assigns) do 7 | # %{errors: %{detail: "Internal Server Error"}} 8 | # end 9 | 10 | # By default, Phoenix returns the status message from 11 | # the template name. For example, "404.json" becomes 12 | # "Not Found". 13 | def template_not_found(template, _assigns) do 14 | %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /elixir/frontend/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Frontend.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :frontend, 7 | version: "0.1.0", 8 | elixir: "~> 1.7", 9 | elixirc_paths: elixirc_paths(Mix.env()), 10 | start_permanent: Mix.env() == :prod, 11 | aliases: aliases(), 12 | deps: deps() 13 | ] 14 | end 15 | 16 | # Configuration for the OTP application. 17 | # 18 | # Type `mix help compile.app` for more information. 19 | def application do 20 | [ 21 | mod: {Frontend.Application, []}, 22 | extra_applications: [:logger, :runtime_tools] 23 | ] 24 | end 25 | 26 | # Specifies which paths to compile per environment. 27 | defp elixirc_paths(:test), do: ["lib", "test/support"] 28 | defp elixirc_paths(_), do: ["lib"] 29 | 30 | # Specifies your project dependencies. 31 | # 32 | # Type `mix help deps` for examples and options. 33 | defp deps do 34 | [ 35 | {:phoenix, "~> 1.7.2"}, 36 | {:phoenix_view, "~> 2.0"}, 37 | {:telemetry_metrics, "~> 0.6.1"}, 38 | {:telemetry_poller, "~> 1.0.0"}, 39 | {:jason, "~> 1.0"}, 40 | {:plug_cowboy, "~> 2.6.1"}, 41 | {:httpoison, "~> 2.1"}, 42 | {:opentelemetry, "~> 1.2.1"}, 43 | {:opentelemetry_api, "~> 1.2.1"}, 44 | {:opentelemetry_phoenix, "~> 1.1.0"}, 45 | {:opentelemetry_exporter, "~> 1.4.0"} 46 | ] 47 | end 48 | 49 | # Aliases are shortcuts or tasks specific to the current project. 50 | # For example, to install project dependencies and perform other setup tasks, run: 51 | # 52 | # $ mix setup 53 | # 54 | # See the documentation for `Mix` for more info on aliases. 55 | defp aliases do 56 | [ 57 | setup: ["deps.get"] 58 | ] 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /elixir/message/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], 4 | import_deps: [:plug] 5 | ] 6 | -------------------------------------------------------------------------------- /elixir/message/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | message-*.tar 24 | 25 | # Temporary files, for example, from tests. 26 | /tmp/ 27 | -------------------------------------------------------------------------------- /elixir/message/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM elixir:1.14 2 | WORKDIR /app 3 | COPY mix.* /app 4 | COPY lib /app/lib 5 | COPY config /app/config 6 | RUN mix local.hex --force 7 | RUN mix local.rebar --force 8 | RUN mix deps.get 9 | RUN mix deps.compile 10 | 11 | EXPOSE 9000 12 | CMD mix run --no-halt 13 | -------------------------------------------------------------------------------- /elixir/message/config/runtime.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | config :opentelemetry, :resource, service: [name: "message-elixir"] 4 | 5 | config :opentelemetry, :propagators, :otel_propagator_http_w3c 6 | 7 | config :opentelemetry, :processors, 8 | otel_batch_processor: %{ 9 | exporter: { 10 | :opentelemetry_exporter, 11 | %{ 12 | endpoints: [ 13 | { 14 | :http, 15 | System.get_env("OTEL_COLLECTOR_HOST", "localhost"), 16 | System.get_env("OTEL_COLLECTOR_PORT", "55681") |> String.to_integer(), 17 | [] 18 | } 19 | ] 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /elixir/message/lib/message.ex: -------------------------------------------------------------------------------- 1 | defmodule Message do 2 | use Plug.Router 3 | 4 | require OpenTelemetry.Tracer, as: Tracer 5 | 6 | plug :match 7 | plug :dispatch 8 | 9 | @messages [ 10 | "how are you?", 11 | "how are you doing?", 12 | "what's good?", 13 | "what's up?", 14 | "how do you do?", 15 | "sup?", 16 | "good day to you", 17 | "how are things?", 18 | "howzit?", 19 | "woohoo" 20 | ] 21 | 22 | get "/message" do 23 | :otel_ctx.attach(:opentelemetry_process_propagator.fetch_parent_ctx()) 24 | 25 | Tracer.with_span "/message" do 26 | Tracer.set_attributes( 27 | "http.method": conn.method, 28 | "http.scheme": conn.scheme, 29 | "http.host": conn.host, 30 | "http.route": conn.request_path 31 | ) 32 | 33 | conn 34 | |> put_resp_content_type("text/plain") 35 | |> send_resp(200, message()) 36 | end 37 | end 38 | 39 | defp message do 40 | Tracer.with_span "📖 look up message ✨" do 41 | :timer.sleep(Enum.random(0..5)) 42 | Enum.random(@messages) 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /elixir/message/lib/message/application.ex: -------------------------------------------------------------------------------- 1 | defmodule Message.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | @impl true 9 | def start(_type, _args) do 10 | children = [ 11 | {Plug.Cowboy, scheme: :http, plug: Message, options: [port: 9000]} 12 | ] 13 | 14 | :opentelemetry_cowboy.setup() 15 | 16 | # See https://hexdocs.pm/elixir/Supervisor.html 17 | # for other strategies and supported options 18 | opts = [strategy: :one_for_one, name: Message.Supervisor] 19 | Supervisor.start_link(children, opts) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /elixir/message/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Message.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :message, 7 | version: "0.1.0", 8 | elixir: "~> 1.12", 9 | start_permanent: Mix.env() == :prod, 10 | deps: deps() 11 | ] 12 | end 13 | 14 | # Run "mix help compile.app" to learn about applications. 15 | def application do 16 | [ 17 | extra_applications: [:logger], 18 | mod: {Message.Application, []} 19 | ] 20 | end 21 | 22 | # Run "mix help deps" to learn about dependencies. 23 | defp deps do 24 | [ 25 | {:plug_cowboy, "~> 2.6"}, 26 | {:opentelemetry, "~> 1.2.0"}, 27 | {:opentelemetry_api, "~> 1.2.1"}, 28 | {:opentelemetry_exporter, "~> 1.4.0"}, 29 | {:opentelemetry_process_propagator, "~> 0.2.2"}, 30 | {:opentelemetry_cowboy, "~> 0.2"}, 31 | {:hackney, "~> 1.17"}, 32 | {:poison, "~> 5.0"} 33 | ] 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /elixir/name/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], 4 | import_deps: [:plug] 5 | ] 6 | -------------------------------------------------------------------------------- /elixir/name/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | name-*.tar 24 | 25 | # Temporary files, for example, from tests. 26 | /tmp/ 27 | -------------------------------------------------------------------------------- /elixir/name/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM elixir:1.14 2 | WORKDIR /app 3 | COPY mix.* /app 4 | COPY lib /app/lib 5 | COPY config /app/config 6 | RUN mix local.hex --force 7 | RUN mix local.rebar --force 8 | RUN mix deps.get 9 | RUN mix deps.compile 10 | 11 | EXPOSE 8000 12 | CMD mix run --no-halt 13 | -------------------------------------------------------------------------------- /elixir/name/config/runtime.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | config :opentelemetry, :resource, service: [name: "name-elixir"] 4 | 5 | config :opentelemetry, :propagators, :otel_propagator_http_w3c 6 | 7 | config :opentelemetry, :processors, 8 | otel_batch_processor: %{ 9 | exporter: { 10 | :opentelemetry_exporter, 11 | %{ 12 | endpoints: [ 13 | { 14 | :http, 15 | System.get_env("OTEL_COLLECTOR_HOST", "localhost"), 16 | System.get_env("OTEL_COLLECTOR_PORT", "55681") |> String.to_integer(), 17 | [] 18 | } 19 | ] 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /elixir/name/lib/name.ex: -------------------------------------------------------------------------------- 1 | defmodule Name do 2 | use Plug.Router 3 | 4 | require OpenTelemetry.Tracer, as: Tracer 5 | 6 | plug :match 7 | plug :dispatch 8 | 9 | @names %{ 10 | 2015 => ~w[sophia jackson emma aiden olivia liam ava lucas mia noah], 11 | 2016 => ~w[sophia jackson emma aiden olivia lucas ava liam mia noah], 12 | 2017 => ~w[sophia jackson olivia liam emma noah ava aiden isabella lucas], 13 | 2018 => ~w[sophia jackson olivia liam emma noah ava aiden isabella caden], 14 | 2019 => ~w[sophia liam olivia jackson emma noah ava aiden aira grayson], 15 | 2020 => ~w[olivia noah emma liam ava elijah isabella oliver sophia lucas] 16 | } 17 | 18 | get "/name" do 19 | conn 20 | |> put_resp_content_type("text/plain") 21 | |> send_resp(200, name_by(year())) 22 | end 23 | 24 | defp name_by(year) do 25 | :otel_ctx.attach(:opentelemetry_process_propagator.fetch_parent_ctx()) 26 | Tracer.with_span "📖 look up name based on year ✨" do 27 | :timer.sleep(Enum.random(1..5)) 28 | Map.get(@names, year) |> Enum.random() 29 | end 30 | end 31 | 32 | defp year do 33 | :otel_ctx.attach(:opentelemetry_process_propagator.fetch_parent_ctx()) 34 | Tracer.with_span "✨ call /year ✨" do 35 | endpoint = System.get_env("YEAR_ENDPOINT", "http://localhost:6001") 36 | headers = :otel_propagator_text_map.inject([]) 37 | HTTPoison.get!("#{endpoint}/year", headers).body |> String.to_integer() 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /elixir/name/lib/name/application.ex: -------------------------------------------------------------------------------- 1 | defmodule Name.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | @impl true 9 | def start(_type, _args) do 10 | children = [ 11 | {Plug.Cowboy, scheme: :http, plug: Name, options: [port: 8000]} 12 | ] 13 | 14 | :opentelemetry_cowboy.setup() 15 | 16 | # See https://hexdocs.pm/elixir/Supervisor.html 17 | # for other strategies and supported options 18 | opts = [strategy: :one_for_one, name: Name.Supervisor] 19 | Supervisor.start_link(children, opts) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /elixir/name/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Name.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :name, 7 | version: "0.1.0", 8 | elixir: "~> 1.12", 9 | start_permanent: Mix.env() == :prod, 10 | deps: deps() 11 | ] 12 | end 13 | 14 | # Run "mix help compile.app" to learn about applications. 15 | def application do 16 | [ 17 | extra_applications: [:logger], 18 | mod: {Name.Application, []} 19 | ] 20 | end 21 | 22 | # Run "mix help deps" to learn about dependencies. 23 | defp deps do 24 | [ 25 | {:plug_cowboy, "~> 2.6"}, 26 | {:opentelemetry, "~> 1.2.1"}, 27 | {:opentelemetry_api, "~> 1.2.1"}, 28 | {:opentelemetry_exporter, "~> 1.4.0"}, 29 | {:opentelemetry_process_propagator, "~> 0.2.2"}, 30 | {:opentelemetry_cowboy, "~> 0.2"}, 31 | {:httpoison, "~> 2.1"} 32 | ] 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /elixir/year/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], 4 | import_deps: [:plug] 5 | ] 6 | -------------------------------------------------------------------------------- /elixir/year/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | year-*.tar 24 | 25 | # Temporary files, for example, from tests. 26 | /tmp/ 27 | -------------------------------------------------------------------------------- /elixir/year/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM elixir:1.14 2 | WORKDIR /app 3 | COPY mix.* /app 4 | COPY lib /app/lib 5 | COPY config /app/config 6 | RUN mix local.hex --force 7 | RUN mix local.rebar --force 8 | RUN mix deps.get 9 | RUN mix deps.compile 10 | 11 | EXPOSE 6001 12 | CMD mix run --no-halt 13 | -------------------------------------------------------------------------------- /elixir/year/config/runtime.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | config :opentelemetry, :resource, service: [name: "year-elixir"] 4 | 5 | config :opentelemetry, :propagators, :otel_propagator_http_w3c 6 | 7 | config :opentelemetry, :processors, 8 | otel_batch_processor: %{ 9 | exporter: { 10 | :opentelemetry_exporter, 11 | %{ 12 | endpoints: [ 13 | { 14 | :http, 15 | System.get_env("OTEL_COLLECTOR_HOST", "localhost"), 16 | System.get_env("OTEL_COLLECTOR_PORT", "55681") |> String.to_integer(), 17 | [] 18 | } 19 | ] 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /elixir/year/lib/year.ex: -------------------------------------------------------------------------------- 1 | defmodule Year do 2 | use Plug.Router 3 | 4 | require OpenTelemetry.Tracer, as: Tracer 5 | 6 | plug :match 7 | plug :dispatch 8 | 9 | get "/year" do 10 | conn 11 | |> put_resp_content_type("text/plain") 12 | |> send_resp(200, year() |> to_string()) 13 | end 14 | 15 | defp year do 16 | :otel_ctx.attach(:opentelemetry_process_propagator.fetch_parent_ctx()) 17 | Tracer.with_span "🗓 get-a-year ✨" do 18 | :timer.sleep(Enum.random(0..5)) 19 | Enum.random(2015..2020) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /elixir/year/lib/year/application.ex: -------------------------------------------------------------------------------- 1 | defmodule Year.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | @impl true 9 | def start(_type, _args) do 10 | children = [ 11 | {Plug.Cowboy, scheme: :http, plug: Year, options: [port: 6001]} 12 | ] 13 | 14 | :opentelemetry_cowboy.setup() 15 | 16 | # See https://hexdocs.pm/elixir/Supervisor.html 17 | # for other strategies and supported options 18 | opts = [strategy: :one_for_one, name: Year.Supervisor] 19 | Supervisor.start_link(children, opts) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /elixir/year/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Year.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :year, 7 | version: "0.1.0", 8 | elixir: "~> 1.12", 9 | start_permanent: Mix.env() == :prod, 10 | deps: deps() 11 | ] 12 | end 13 | 14 | # Run "mix help compile.app" to learn about applications. 15 | def application do 16 | [ 17 | extra_applications: [:logger], 18 | mod: {Year.Application, []} 19 | ] 20 | end 21 | 22 | # Run "mix help deps" to learn about dependencies. 23 | defp deps do 24 | [ 25 | {:plug_cowboy, "~> 2.6"}, 26 | {:opentelemetry, "~> 1.2.0"}, 27 | {:opentelemetry_api, "~> 1.2.1"}, 28 | {:opentelemetry_exporter, "~> 1.4.0"}, 29 | {:opentelemetry_process_propagator, "~> 0.2.2"}, 30 | {:opentelemetry_cowboy, "~> 0.2"} 31 | ] 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /go-auto-instrumented/.dockerignore: -------------------------------------------------------------------------------- 1 | **/bin/ 2 | **/obj/ 3 | -------------------------------------------------------------------------------- /go-auto-instrumented/README.md: -------------------------------------------------------------------------------- 1 | # auto-instrument go services 2 | 3 | ## setup 4 | 5 | ```sh 6 | # replace `` with actual api key 7 | export HONEYCOMB_API_KEY= 8 | 9 | # build the docker images for all the services 10 | docker-compose build 11 | 12 | # to run with Kind using locally built images, see step below and come back 13 | 14 | # deploy the greetings namespace and services in k8s 15 | kubectl apply -k greetings/ 16 | 17 | # create secret with api key 18 | kubectl create secret generic honeycomb --from-literal=api-key=$HONEYCOMB_API_KEY -n greetings 19 | 20 | # deploy the services with the auto-instrumentation agent 21 | kubectl apply -f greetings-instrumented.yaml -n greetings 22 | 23 | # deploy the collector 24 | kubectl apply -f otel-collector.yaml -n greetings 25 | 26 | # if running with kind, port-forward the frontend service (not necessary otherwise bc of loadbalancer) 27 | kubectl port-forward svc/frontend 7007:7007 -n greetings 28 | 29 | # make sure everything is up and running 30 | kubectl get pods -n greetings 31 | 32 | # follow logs for collector (optional) 33 | kubectl logs deployments/otel-collector --follow -n greetings 34 | ``` 35 | 36 | `curl localhost:7007/greeting` 37 | 38 | ### run with kind 39 | 40 | after buildings things locally... 41 | 42 | ```sh 43 | # create cluster with name kind 44 | kind create cluster 45 | 46 | # load locally built docker images into kind 47 | kind load docker-image frontend-go-auto:local message-go-auto:local name-go-auto:local year-go-auto:local otel-go-instrumentation:v0.1 otel/opentelemetry-collector:0.75.0 48 | ``` 49 | 50 | ## cleanup 51 | 52 | ```sh 53 | # delete secret with api key 54 | kubectl delete namespace greetings 55 | 56 | # make sure everything is gone 57 | kubectl get pods -n greetings 58 | 59 | # if using kind, delete the cluster 60 | kind delete cluster 61 | ``` 62 | -------------------------------------------------------------------------------- /go-auto-instrumented/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.4" 2 | 3 | services: 4 | frontend: 5 | build: 6 | context: . 7 | dockerfile: ./frontend/Dockerfile 8 | image: frontend-go-auto:local 9 | ports: 10 | - 7007:7007 11 | 12 | message: 13 | build: 14 | context: . 15 | dockerfile: ./message-service/Dockerfile 16 | image: message-go-auto:local 17 | ports: 18 | - 9000:9000 19 | 20 | name: 21 | build: 22 | context: . 23 | dockerfile: ./name-service/Dockerfile 24 | image: name-go-auto:local 25 | ports: 26 | - 8000:8000 27 | 28 | year: 29 | build: 30 | context: . 31 | dockerfile: ./year-service/Dockerfile 32 | image: year-go-auto:local 33 | ports: 34 | - 6001:6001 35 | 36 | -------------------------------------------------------------------------------- /go-auto-instrumented/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | # to be built from docker-compose 2 | FROM golang:1.20 AS build 3 | WORKDIR /src 4 | ENV CGO_ENABLED=0 5 | 6 | COPY go.mod . 7 | COPY frontend/. . 8 | RUN go get 9 | RUN go build -o /out/frontend . 10 | 11 | FROM scratch AS bin 12 | WORKDIR /app 13 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 14 | COPY --from=build /out/frontend /app/ 15 | 16 | # local code uses localhost:9000 and localhost:8000 17 | # image will use the below endpoints 18 | ENV MESSAGE_ENDPOINT=http://message:9000 19 | ENV NAME_ENDPOINT=http://name:8000 20 | # or override at runtime in k8s or docker-compose 21 | 22 | CMD ["/app/frontend"] 23 | -------------------------------------------------------------------------------- /go-auto-instrumented/frontend/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "github.com/gorilla/mux" 11 | ) 12 | 13 | func getEnv(key, fallback string) string { 14 | value := os.Getenv(key) 15 | if len(value) == 0 { 16 | return fallback 17 | } 18 | return value 19 | } 20 | 21 | var ( 22 | nameServiceUrl = getEnv("NAME_ENDPOINT", "http://localhost:8000") + "/name" 23 | messageServiceUrl = getEnv("MESSAGE_ENDPOINT", "http://localhost:9000") + "/message" 24 | ) 25 | 26 | func main() { 27 | 28 | r := mux.NewRouter() 29 | r.HandleFunc("/greeting", func(w http.ResponseWriter, r *http.Request) { 30 | name := getName(r.Context()) 31 | message := getMessage(r.Context()) 32 | 33 | _, _ = fmt.Fprintf(w, "Hello %s, %s", name, message) 34 | }) 35 | 36 | log.Println("Listening on http://localhost:7007/greeting") 37 | log.Fatal(http.ListenAndServe(":7007", r)) 38 | } 39 | 40 | func getName(ctx context.Context) string { 41 | return makeRequest(ctx, nameServiceUrl) 42 | } 43 | 44 | func getMessage(ctx context.Context) string { 45 | return makeRequest(ctx, messageServiceUrl) 46 | } 47 | 48 | func makeRequest(ctx context.Context, url string) string { 49 | req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) 50 | client := http.Client{Transport: http.DefaultTransport} 51 | res, err := client.Do(req) 52 | if err != nil { 53 | panic(err) 54 | } 55 | body, err := ioutil.ReadAll(res.Body) 56 | res.Body.Close() 57 | if err != nil { 58 | panic(err) 59 | } 60 | return string(body) 61 | } 62 | -------------------------------------------------------------------------------- /go-auto-instrumented/go.mod: -------------------------------------------------------------------------------- 1 | module go-auto-instrumented 2 | 3 | go 1.18 4 | 5 | require github.com/gorilla/mux v1.8.0 6 | -------------------------------------------------------------------------------- /go-auto-instrumented/go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 2 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 3 | -------------------------------------------------------------------------------- /go-auto-instrumented/greetings/frontend.yaml: -------------------------------------------------------------------------------- 1 | ################################################################################################## 2 | # frontend-go services 3 | ################################################################################################## 4 | kind: ServiceAccount 5 | apiVersion: v1 6 | metadata: 7 | name: frontend-go 8 | namespace: greetings 9 | --- 10 | apiVersion: v1 11 | kind: Service 12 | metadata: 13 | name: frontend 14 | namespace: greetings 15 | labels: 16 | app: frontend 17 | service: frontend 18 | app.kubernetes.io/name: frontend-go 19 | spec: 20 | selector: 21 | app: frontend 22 | ports: 23 | - name: http 24 | port: 7007 25 | targetPort: 7007 26 | type: LoadBalancer 27 | --- 28 | apiVersion: apps/v1 29 | kind: Deployment 30 | metadata: 31 | name: frontend-go 32 | namespace: greetings 33 | labels: 34 | app: frontend-go 35 | app.kubernetes.io/name: frontend-go 36 | spec: 37 | replicas: 1 38 | selector: 39 | matchLabels: 40 | app: frontend 41 | template: 42 | metadata: 43 | labels: 44 | app: frontend 45 | spec: 46 | serviceAccountName: frontend-go 47 | terminationGracePeriodSeconds: 0 48 | containers: 49 | - name: frontend 50 | image: frontend-go-auto:local 51 | ports: 52 | - containerPort: 7007 53 | name: http 54 | env: 55 | - name: MESSAGE_ENDPOINT 56 | value: http://message:9000 57 | - name: NAME_ENDPOINT 58 | value: http://name:8000 59 | -------------------------------------------------------------------------------- /go-auto-instrumented/greetings/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ns.yaml 3 | - frontend.yaml 4 | - message.yaml 5 | - name.yaml 6 | - year.yaml 7 | -------------------------------------------------------------------------------- /go-auto-instrumented/greetings/message.yaml: -------------------------------------------------------------------------------- 1 | ################################################################################################## 2 | # message-go services 3 | ################################################################################################## 4 | kind: ServiceAccount 5 | apiVersion: v1 6 | metadata: 7 | name: message-go 8 | namespace: greetings 9 | --- 10 | apiVersion: v1 11 | kind: Service 12 | metadata: 13 | name: message 14 | namespace: greetings 15 | labels: 16 | app: message 17 | service: message 18 | app.kubernetes.io/name: message-go 19 | spec: 20 | selector: 21 | app: message 22 | ports: 23 | - name: http 24 | port: 9000 25 | targetPort: 9000 26 | type: LoadBalancer 27 | --- 28 | apiVersion: apps/v1 29 | kind: Deployment 30 | metadata: 31 | name: message-go 32 | namespace: greetings 33 | labels: 34 | app: message-go 35 | app.kubernetes.io/name: message-go 36 | spec: 37 | replicas: 1 38 | selector: 39 | matchLabels: 40 | app: message 41 | template: 42 | metadata: 43 | labels: 44 | app: message 45 | spec: 46 | serviceAccountName: message-go 47 | terminationGracePeriodSeconds: 0 48 | containers: 49 | - name: message 50 | image: message-go-auto:local 51 | ports: 52 | - containerPort: 9000 53 | name: http 54 | -------------------------------------------------------------------------------- /go-auto-instrumented/greetings/name.yaml: -------------------------------------------------------------------------------- 1 | ################################################################################################## 2 | # name-go services 3 | ################################################################################################## 4 | kind: ServiceAccount 5 | apiVersion: v1 6 | metadata: 7 | name: name-go 8 | namespace: greetings 9 | --- 10 | apiVersion: v1 11 | kind: Service 12 | metadata: 13 | name: name 14 | namespace: greetings 15 | labels: 16 | app: name 17 | service: name 18 | app.kubernetes.io/name: name-go 19 | spec: 20 | selector: 21 | app: name 22 | ports: 23 | - name: http 24 | port: 8000 25 | targetPort: 8000 26 | type: LoadBalancer 27 | --- 28 | apiVersion: apps/v1 29 | kind: Deployment 30 | metadata: 31 | name: name-go 32 | namespace: greetings 33 | labels: 34 | app: name-go 35 | app.kubernetes.io/name: name-go 36 | spec: 37 | replicas: 1 38 | selector: 39 | matchLabels: 40 | app: name 41 | template: 42 | metadata: 43 | labels: 44 | app: name 45 | spec: 46 | serviceAccountName: name-go 47 | terminationGracePeriodSeconds: 0 48 | containers: 49 | - name: name 50 | image: name-go-auto:local 51 | ports: 52 | - containerPort: 8000 53 | name: http 54 | env: 55 | - name: YEAR_ENDPOINT 56 | value: http://year:6001 57 | -------------------------------------------------------------------------------- /go-auto-instrumented/greetings/ns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: greetings 5 | -------------------------------------------------------------------------------- /go-auto-instrumented/greetings/year.yaml: -------------------------------------------------------------------------------- 1 | ################################################################################################## 2 | # year-go services 3 | ################################################################################################## 4 | kind: ServiceAccount 5 | apiVersion: v1 6 | metadata: 7 | name: year-go 8 | namespace: greetings 9 | --- 10 | apiVersion: v1 11 | kind: Service 12 | metadata: 13 | name: year 14 | namespace: greetings 15 | labels: 16 | app: year 17 | service: year 18 | app.kubernetes.io/name: year-go 19 | spec: 20 | selector: 21 | app: year 22 | ports: 23 | - name: http 24 | port: 6001 25 | targetPort: 6001 26 | type: LoadBalancer 27 | --- 28 | apiVersion: apps/v1 29 | kind: Deployment 30 | metadata: 31 | name: year-go 32 | namespace: greetings 33 | labels: 34 | app: year-go 35 | app.kubernetes.io/name: year-go 36 | spec: 37 | replicas: 1 38 | selector: 39 | matchLabels: 40 | app: year 41 | template: 42 | metadata: 43 | labels: 44 | app: year 45 | spec: 46 | serviceAccountName: year-go 47 | terminationGracePeriodSeconds: 0 48 | containers: 49 | - name: year 50 | image: year-go-auto:local 51 | ports: 52 | - containerPort: 6001 53 | name: http 54 | 55 | -------------------------------------------------------------------------------- /go-auto-instrumented/message-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # to be built from docker-compose 2 | FROM golang:1.20 AS build 3 | WORKDIR /src 4 | ENV CGO_ENABLED=0 5 | 6 | COPY go.mod . 7 | 8 | COPY message-service/. . 9 | RUN go build -o /out/message-service . 10 | 11 | FROM scratch AS bin 12 | WORKDIR /app 13 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 14 | COPY --from=build /out/message-service /app/ 15 | CMD ["/app/message-service"] 16 | -------------------------------------------------------------------------------- /go-auto-instrumented/message-service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | messages := []string{ 13 | "how are you?", "how are you doing?", "what's good?", "what's up?", "how do you do?", 14 | "sup?", "good day to you", "how are things?", "howzit?", "woohoo", 15 | } 16 | 17 | mux := http.NewServeMux() 18 | mux.HandleFunc("/message", func(w http.ResponseWriter, r *http.Request) { 19 | rand.Seed(time.Now().UnixNano()) 20 | time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond) 21 | _, _ = fmt.Fprintf(w, messages[rand.Intn(len(messages))]) 22 | }) 23 | handler := http.Handler(mux) 24 | 25 | log.Println("Listening on http://localhost:9000/message") 26 | log.Fatal(http.ListenAndServe(":9000",handler)) 27 | } 28 | -------------------------------------------------------------------------------- /go-auto-instrumented/name-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # to be built from docker-compose 2 | FROM golang:1.20 AS build 3 | WORKDIR /src 4 | ENV CGO_ENABLED=0 5 | 6 | COPY go.mod . 7 | 8 | COPY name-service/. . 9 | RUN go build -o /out/name-service . 10 | 11 | FROM scratch AS bin 12 | WORKDIR /app 13 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 14 | COPY --from=build /out/name-service /app/ 15 | 16 | # local code uses localhost:6001/year 17 | # image will use the below endpoint 18 | ENV YEAR_ENDPOINT=http://year:6001 19 | # or override at runtime in k8s or docker-compose 20 | 21 | CMD ["/app/name-service"] 22 | -------------------------------------------------------------------------------- /go-auto-instrumented/name-service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "math/rand" 9 | "net/http" 10 | "os" 11 | "strconv" 12 | "time" 13 | ) 14 | func getEnv(key, fallback string) string { 15 | value := os.Getenv(key) 16 | if len(value) == 0 { 17 | return fallback 18 | } 19 | return value 20 | } 21 | 22 | var ( 23 | yearServiceUrl = getEnv("YEAR_ENDPOINT", "http://localhost:6001") + "/year" 24 | ) 25 | 26 | func main() { 27 | // initialize the random number generator 28 | rand.Seed(time.Now().UnixNano()) 29 | 30 | namesByYear := map[int][]string{ 31 | 2016: {"sophia", "jackson", "emma", "aiden", "olivia", "lucas", "ava", "liam", "mia", "noah"}, 32 | 2017: {"sophia", "jackson", "olivia", "liam", "emma", "noah", "ava", "aiden", "isabella", "lucas"}, 33 | 2018: {"sophia", "jackson", "olivia", "liam", "emma", "noah", "ava", "aiden", "isabella", "caden"}, 34 | 2019: {"sophia", "liam", "olivia", "jackson", "emma", "noah", "ava", "aiden", "aria", "grayson"}, 35 | 2020: {"olivia", "noah", "emma", "liam", "ava", "elijah", "isabella", "oliver", "sophia", "lucas"}, 36 | } 37 | 38 | mux := http.NewServeMux() 39 | mux.HandleFunc("/name", func(w http.ResponseWriter, r *http.Request) { 40 | time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond) 41 | year, _ := getYear(r.Context()) 42 | names := namesByYear[year] 43 | _, _ = fmt.Fprintf(w, names[rand.Intn(len(names))]) 44 | }) 45 | 46 | handler := http.Handler(mux) 47 | 48 | log.Println("Listening on http://localhost:8000/name") 49 | log.Fatal(http.ListenAndServe(":8000", handler)) 50 | } 51 | 52 | func getYear(ctx context.Context) (int, context.Context) { 53 | req, err := http.NewRequestWithContext(ctx, "GET", yearServiceUrl, nil) 54 | if err != nil { 55 | fmt.Printf("error creating request: %s", err) 56 | } 57 | client := http.Client{Transport: http.DefaultTransport} 58 | res, err := client.Do(req) 59 | if err != nil { 60 | panic(err) 61 | } 62 | body, err := ioutil.ReadAll(res.Body) 63 | res.Body.Close() 64 | if err != nil { 65 | panic(err) 66 | } 67 | year, err := strconv.Atoi(string(body)) 68 | if err != nil { 69 | panic(err) 70 | } 71 | return year, ctx 72 | } 73 | -------------------------------------------------------------------------------- /go-auto-instrumented/year-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # to be built from docker-compose 2 | FROM golang:1.20 AS build 3 | WORKDIR /src 4 | ENV CGO_ENABLED=0 5 | 6 | COPY go.mod . 7 | 8 | COPY year-service/*.go . 9 | RUN go build -o /out/year-service . 10 | 11 | FROM scratch AS bin 12 | WORKDIR /app 13 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 14 | COPY --from=build /out/year-service /app/ 15 | CMD ["/app/year-service"] 16 | -------------------------------------------------------------------------------- /go-auto-instrumented/year-service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "math/rand" 8 | "net/http" 9 | "time" 10 | ) 11 | 12 | func calculateYear() int { 13 | years := []int{2016, 2017, 2018, 2019, 2020} 14 | rand.Seed(time.Now().UnixNano()) 15 | time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond) 16 | return years[rand.Intn(len(years))] 17 | } 18 | 19 | func yearHandler(w http.ResponseWriter, r *http.Request) { 20 | ctx := r.Context() 21 | 22 | year := func(ctx context.Context) int { 23 | return calculateYear() 24 | }(ctx) 25 | 26 | _, _ = fmt.Fprintf(w, "%d", year) 27 | } 28 | 29 | func main() { 30 | mux := http.NewServeMux() 31 | mux.HandleFunc("/year", yearHandler) 32 | 33 | handler := http.Handler(mux) 34 | 35 | log.Println("Listening on http://localhost:6001/year") 36 | log.Fatal(http.ListenAndServe(":6001", handler)) 37 | } 38 | -------------------------------------------------------------------------------- /go-uninstrumented/.dockerignore: -------------------------------------------------------------------------------- 1 | **/bin/ 2 | **/obj/ 3 | -------------------------------------------------------------------------------- /go-uninstrumented/README.md: -------------------------------------------------------------------------------- 1 | # example greeting service in golang - uninstrumented 2 | 3 | Images are now available on [GitHub Packages](https://github.com/orgs/honeycombio/packages?repo_name=example-greeting-service) 4 | 5 | ## Steps to Run in Kubernetes 6 | 7 | ```sh 8 | # deploy the services in k8s 9 | kubectl apply -f greetings.yaml 10 | # port-forward the frontend service 11 | kubectl port-forward svc/frontend 7777:7777 12 | ``` 13 | 14 | Then in a new terminal, `curl localhost:7777/greeting` 15 | 16 | When all done: 17 | 18 | ```sh 19 | kubectl delete -f greetings.yaml 20 | ``` 21 | 22 | ## Steps to Build Locally 23 | 24 | To use local images, build using `docker-compose` and update the `greetings.yaml` with the new image names. 25 | 26 | ```sh 27 | # build docker images 28 | docker-compose build 29 | ``` 30 | 31 | As an example: 32 | 33 | ```yaml 34 | spec: 35 | serviceAccountName: year-go 36 | terminationGracePeriodSeconds: 0 37 | containers: 38 | - name: year 39 | imagePullPolicy: IfNotPresent 40 | image: egs-year-go:local 41 | ports: 42 | - containerPort: 6001 43 | name: http 44 | ``` 45 | -------------------------------------------------------------------------------- /go-uninstrumented/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.4" 2 | 3 | services: 4 | frontend: 5 | build: 6 | context: . 7 | dockerfile: ./frontend-service/Dockerfile 8 | image: egs-frontend-go:local 9 | ports: 10 | - 7777:7777 11 | 12 | message: 13 | build: 14 | context: . 15 | dockerfile: ./message-service/Dockerfile 16 | image: egs-message-go:local 17 | ports: 18 | - 9000:9000 19 | 20 | name: 21 | build: 22 | context: . 23 | dockerfile: ./name-service/Dockerfile 24 | image: egs-name-go:local 25 | ports: 26 | - 8000:8000 27 | 28 | year: 29 | build: 30 | context: . 31 | dockerfile: ./year-service/Dockerfile 32 | image: egs-year-go:local 33 | ports: 34 | - 6001:6001 35 | 36 | -------------------------------------------------------------------------------- /go-uninstrumented/frontend-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # to be built from docker-compose or publish-ghcr workflow 2 | FROM golang:1.20 AS build 3 | WORKDIR /src 4 | ENV CGO_ENABLED=0 5 | 6 | COPY go.mod . 7 | COPY frontend-service/. . 8 | RUN go get 9 | RUN go build -o /out/frontend-service . 10 | 11 | FROM scratch AS bin 12 | WORKDIR /app 13 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 14 | COPY --from=build /out/frontend-service /app/ 15 | 16 | # local code uses localhost:9000 and localhost:8000 17 | # image will use the below endpoints 18 | ENV MESSAGE_ENDPOINT=http://message:9000 19 | ENV NAME_ENDPOINT=http://name:8000 20 | # or override at runtime in k8s or docker-compose 21 | 22 | CMD ["/app/frontend-service"] 23 | -------------------------------------------------------------------------------- /go-uninstrumented/frontend-service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "github.com/gorilla/mux" 11 | ) 12 | 13 | func getEnv(key, fallback string) string { 14 | value := os.Getenv(key) 15 | if len(value) == 0 { 16 | return fallback 17 | } 18 | return value 19 | } 20 | 21 | var ( 22 | nameServiceUrl = getEnv("NAME_ENDPOINT", "http://localhost:8000") + "/name" 23 | messageServiceUrl = getEnv("MESSAGE_ENDPOINT", "http://localhost:9000") + "/message" 24 | ) 25 | 26 | func main() { 27 | 28 | r := mux.NewRouter() 29 | r.HandleFunc("/greeting", func(w http.ResponseWriter, r *http.Request) { 30 | name := getName(r.Context()) 31 | message := getMessage(r.Context()) 32 | 33 | _, _ = fmt.Fprintf(w, "Hello %s, %s", name, message) 34 | }) 35 | 36 | log.Println("Listening on http://localhost:7777/greeting") 37 | log.Fatal(http.ListenAndServe(":7777", r)) 38 | } 39 | 40 | func getName(ctx context.Context) string { 41 | return makeRequest(ctx, nameServiceUrl) 42 | } 43 | 44 | func getMessage(ctx context.Context) string { 45 | return makeRequest(ctx, messageServiceUrl) 46 | } 47 | 48 | func makeRequest(ctx context.Context, url string) string { 49 | req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) 50 | client := http.Client{Transport: http.DefaultTransport} 51 | res, err := client.Do(req) 52 | if err != nil { 53 | panic(err) 54 | } 55 | body, err := ioutil.ReadAll(res.Body) 56 | res.Body.Close() 57 | if err != nil { 58 | panic(err) 59 | } 60 | return string(body) 61 | } 62 | -------------------------------------------------------------------------------- /go-uninstrumented/go.mod: -------------------------------------------------------------------------------- 1 | module go-auto-instrumented 2 | 3 | go 1.18 4 | 5 | require github.com/gorilla/mux v1.8.0 6 | -------------------------------------------------------------------------------- /go-uninstrumented/go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 2 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 3 | -------------------------------------------------------------------------------- /go-uninstrumented/message-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # to be built from docker-compose or publish-ghcr workflow 2 | FROM golang:1.20 AS build 3 | WORKDIR /src 4 | ENV CGO_ENABLED=0 5 | 6 | COPY go.mod . 7 | 8 | COPY message-service/. . 9 | RUN go build -o /out/message-service . 10 | 11 | FROM scratch AS bin 12 | WORKDIR /app 13 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 14 | COPY --from=build /out/message-service /app/ 15 | CMD ["/app/message-service"] 16 | -------------------------------------------------------------------------------- /go-uninstrumented/message-service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | messages := []string{ 13 | "how are you?", "how are you doing?", "what's good?", "what's up?", "how do you do?", 14 | "sup?", "good day to you", "how are things?", "howzit?", "woohoo", 15 | } 16 | 17 | mux := http.NewServeMux() 18 | mux.HandleFunc("/message", func(w http.ResponseWriter, r *http.Request) { 19 | rand.Seed(time.Now().UnixNano()) 20 | time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond) 21 | _, _ = fmt.Fprintf(w, messages[rand.Intn(len(messages))]) 22 | }) 23 | handler := http.Handler(mux) 24 | 25 | log.Println("Listening on http://localhost:9000/message") 26 | log.Fatal(http.ListenAndServe(":9000",handler)) 27 | } 28 | -------------------------------------------------------------------------------- /go-uninstrumented/name-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # to be built from docker-compose or publish-ghcr workflow 2 | FROM golang:1.20 AS build 3 | WORKDIR /src 4 | ENV CGO_ENABLED=0 5 | 6 | COPY go.mod . 7 | 8 | COPY name-service/. . 9 | RUN go build -o /out/name-service . 10 | 11 | FROM scratch AS bin 12 | WORKDIR /app 13 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 14 | COPY --from=build /out/name-service /app/ 15 | 16 | # local code uses localhost:6001/year 17 | # image will use the below endpoint 18 | ENV YEAR_ENDPOINT=http://year:6001 19 | # or override at runtime in k8s or docker-compose 20 | 21 | CMD ["/app/name-service"] 22 | -------------------------------------------------------------------------------- /go-uninstrumented/name-service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "math/rand" 9 | "net/http" 10 | "os" 11 | "strconv" 12 | "time" 13 | ) 14 | func getEnv(key, fallback string) string { 15 | value := os.Getenv(key) 16 | if len(value) == 0 { 17 | return fallback 18 | } 19 | return value 20 | } 21 | 22 | var ( 23 | yearServiceUrl = getEnv("YEAR_ENDPOINT", "http://localhost:6001") + "/year" 24 | ) 25 | 26 | func main() { 27 | // initialize the random number generator 28 | rand.Seed(time.Now().UnixNano()) 29 | 30 | namesByYear := map[int][]string{ 31 | 2016: {"sophia", "jackson", "emma", "aiden", "olivia", "lucas", "ava", "liam", "mia", "noah"}, 32 | 2017: {"sophia", "jackson", "olivia", "liam", "emma", "noah", "ava", "aiden", "isabella", "lucas"}, 33 | 2018: {"sophia", "jackson", "olivia", "liam", "emma", "noah", "ava", "aiden", "isabella", "caden"}, 34 | 2019: {"sophia", "liam", "olivia", "jackson", "emma", "noah", "ava", "aiden", "aria", "grayson"}, 35 | 2020: {"olivia", "noah", "emma", "liam", "ava", "elijah", "isabella", "oliver", "sophia", "lucas"}, 36 | } 37 | 38 | mux := http.NewServeMux() 39 | mux.HandleFunc("/name", func(w http.ResponseWriter, r *http.Request) { 40 | time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond) 41 | year, _ := getYear(r.Context()) 42 | names := namesByYear[year] 43 | _, _ = fmt.Fprintf(w, names[rand.Intn(len(names))]) 44 | }) 45 | 46 | handler := http.Handler(mux) 47 | 48 | log.Println("Listening on http://localhost:8000/name") 49 | log.Fatal(http.ListenAndServe(":8000", handler)) 50 | } 51 | 52 | func getYear(ctx context.Context) (int, context.Context) { 53 | req, err := http.NewRequestWithContext(ctx, "GET", yearServiceUrl, nil) 54 | if err != nil { 55 | fmt.Printf("error creating request: %s", err) 56 | } 57 | client := http.Client{Transport: http.DefaultTransport} 58 | res, err := client.Do(req) 59 | if err != nil { 60 | panic(err) 61 | } 62 | body, err := ioutil.ReadAll(res.Body) 63 | res.Body.Close() 64 | if err != nil { 65 | panic(err) 66 | } 67 | year, err := strconv.Atoi(string(body)) 68 | if err != nil { 69 | panic(err) 70 | } 71 | return year, ctx 72 | } 73 | -------------------------------------------------------------------------------- /go-uninstrumented/year-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # to be built from docker-compose or publish-ghcr workflow 2 | FROM golang:1.20 AS build 3 | WORKDIR /src 4 | ENV CGO_ENABLED=0 5 | 6 | COPY go.mod . 7 | 8 | COPY year-service/*.go . 9 | RUN go build -o /out/year-service . 10 | 11 | FROM scratch AS bin 12 | WORKDIR /app 13 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 14 | COPY --from=build /out/year-service /app/ 15 | CMD ["/app/year-service"] 16 | -------------------------------------------------------------------------------- /go-uninstrumented/year-service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "math/rand" 8 | "net/http" 9 | "time" 10 | ) 11 | 12 | func calculateYear() int { 13 | years := []int{2016, 2017, 2018, 2019, 2020} 14 | rand.Seed(time.Now().UnixNano()) 15 | time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond) 16 | return years[rand.Intn(len(years))] 17 | } 18 | 19 | func yearHandler(w http.ResponseWriter, r *http.Request) { 20 | ctx := r.Context() 21 | 22 | year := func(ctx context.Context) int { 23 | return calculateYear() 24 | }(ctx) 25 | 26 | _, _ = fmt.Fprintf(w, "%d", year) 27 | } 28 | 29 | func main() { 30 | mux := http.NewServeMux() 31 | mux.HandleFunc("/year", yearHandler) 32 | 33 | handler := http.Handler(mux) 34 | 35 | log.Println("Listening on http://localhost:6001/year") 36 | log.Fatal(http.ListenAndServe(":6001", handler)) 37 | } 38 | -------------------------------------------------------------------------------- /golang/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # When opening a file, EditorConfig plugins look for a file named .editorconfig 4 | # in the directory of the opened file and in every parent directory. 5 | # A search for .editorconfig files will stop if the root filepath is reached 6 | # or an EditorConfig file with root=true is found. 7 | 8 | # EditorConfig files are read top to bottom and the most recent rules found 9 | # take precedence. Properties from matching EditorConfig sections are applied 10 | # in the order they were read, so properties in closer files take precedence. 11 | 12 | # go files 13 | [*.go] 14 | 15 | indent_size = 8 16 | indent_style = tab 17 | tab_width = 8 -------------------------------------------------------------------------------- /golang/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.4" 2 | 3 | x-common-env: &common-env 4 | HONEYCOMB_API_KEY: ${HONEYCOMB_API_KEY} 5 | OTEL_EXPORTER_OTLP_ENDPOINT: api.honeycomb.io 6 | OTEL_RESOURCE_ATTRIBUTES: app.running-in=docker 7 | MESSAGE_ENDPOINT: http://message:9000 8 | NAME_ENDPOINT: http://name:8000 9 | YEAR_ENDPOINT: http://year:6001 10 | REDIS_URL: redis 11 | 12 | services: 13 | frontend: 14 | build: 15 | context: . 16 | dockerfile: ./frontend/Dockerfile 17 | image: hnyexample/frontend-golang 18 | environment: 19 | <<: *common-env 20 | OTEL_SERVICE_NAME: frontend-go 21 | ports: 22 | - 7777:7777 23 | 24 | message: 25 | build: 26 | context: . 27 | dockerfile: ./message-service/Dockerfile 28 | image: hnyexample/message-golang 29 | environment: 30 | <<: *common-env 31 | OTEL_SERVICE_NAME: message-go 32 | ports: 33 | - 9000:9000 34 | 35 | name: 36 | build: 37 | context: . 38 | dockerfile: ./name-service/Dockerfile 39 | image: hnyexample/name-golang 40 | environment: 41 | <<: *common-env 42 | OTEL_SERVICE_NAME: name-go 43 | ports: 44 | - 8000:8000 45 | 46 | year: 47 | build: 48 | context: . 49 | dockerfile: ./year-service/Dockerfile 50 | image: hnyexample/year-golang 51 | environment: 52 | <<: *common-env 53 | OTEL_SERVICE_NAME: year-go 54 | ports: 55 | - 6001:6001 56 | 57 | redis: 58 | image: redis:latest 59 | ports: 60 | - "127.0.0.1:6379:6379" 61 | 62 | -------------------------------------------------------------------------------- /golang/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.20 AS build 2 | WORKDIR /src 3 | ENV CGO_ENABLED=0 4 | 5 | COPY go.mod . 6 | COPY go.sum . 7 | COPY frontend/. . 8 | 9 | RUN go get 10 | RUN go build -o /out/frontend . 11 | 12 | FROM scratch AS bin 13 | WORKDIR /app 14 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 15 | COPY --from=build /out/frontend /app/ 16 | CMD ["/app/frontend"] 17 | -------------------------------------------------------------------------------- /golang/message-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.20 AS build 2 | WORKDIR /src 3 | ENV CGO_ENABLED=0 4 | 5 | COPY go.mod . 6 | COPY go.sum . 7 | COPY message-service/. . 8 | 9 | RUN go build -o /out/message-service . 10 | 11 | FROM scratch AS bin 12 | WORKDIR /app 13 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 14 | COPY --from=build /out/message-service /app/ 15 | CMD ["/app/message-service"] 16 | -------------------------------------------------------------------------------- /golang/message-service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "net/http" 8 | "os" 9 | "time" 10 | 11 | "github.com/honeycombio/beeline-go" 12 | "github.com/honeycombio/beeline-go/propagation" 13 | "github.com/honeycombio/beeline-go/wrappers/config" 14 | "github.com/honeycombio/beeline-go/wrappers/hnynethttp" 15 | ) 16 | 17 | func getHttpEndpoint() string { 18 | apiEndpoint, exists := os.LookupEnv("HONEYCOMB_API_ENDPOINT") 19 | if !exists { 20 | apiEndpoint = "https://api.honeycomb.io" 21 | } 22 | return apiEndpoint 23 | } 24 | 25 | func traceParserHook(r *http.Request) *propagation.PropagationContext { 26 | headers := map[string]string{ 27 | "traceparent": r.Header.Get("traceparent"), 28 | } 29 | ctx := r.Context() 30 | ctx, prop, err := propagation.UnmarshalW3CTraceContext(ctx, headers) 31 | if err != nil { 32 | fmt.Println("Error unmarshaling header") 33 | fmt.Println(err) 34 | } 35 | return prop 36 | } 37 | 38 | func main() { 39 | beeline.Init(beeline.Config{ 40 | APIHost: getHttpEndpoint(), 41 | WriteKey: os.Getenv("HONEYCOMB_API_KEY"), 42 | Dataset: os.Getenv("HONEYCOMB_DATASET"), 43 | ServiceName: "message-go", 44 | }) 45 | defer beeline.Close() 46 | 47 | messages := []string{ 48 | "how are you?", "how are you doing?", "what's good?", "what's up?", "how do you do?", 49 | "sup?", "good day to you", "how are things?", "howzit?", "woohoo", 50 | } 51 | 52 | mux := http.NewServeMux() 53 | mux.HandleFunc("/message", func(w http.ResponseWriter, r *http.Request) { 54 | ctx := r.Context() 55 | ctx, span := beeline.StartSpan(ctx, "📖 look up message ✨") 56 | defer span.Send() 57 | rand.Seed(time.Now().UnixNano()) 58 | time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond) 59 | _, _ = fmt.Fprintf(w, messages[rand.Intn(len(messages))]) 60 | }) 61 | 62 | log.Println("Listening on http://localhost:9000/message") 63 | log.Fatal( 64 | http.ListenAndServe( 65 | ":9000", 66 | hnynethttp.WrapHandlerWithConfig( 67 | mux, 68 | config.HTTPIncomingConfig{ 69 | HTTPParserHook: traceParserHook}))) 70 | } 71 | -------------------------------------------------------------------------------- /golang/name-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.20 AS build 2 | WORKDIR /src 3 | ENV CGO_ENABLED=0 4 | 5 | COPY go.mod . 6 | COPY go.sum . 7 | COPY name-service/. . 8 | 9 | RUN go build -o /out/name-service ./main.go 10 | 11 | FROM scratch AS bin 12 | WORKDIR /app 13 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 14 | COPY --from=build /out/name-service /app/ 15 | CMD ["/app/name-service"] 16 | -------------------------------------------------------------------------------- /golang/year-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.20 AS build 2 | WORKDIR /src 3 | ENV CGO_ENABLED=0 4 | 5 | COPY go.mod . 6 | COPY go.sum . 7 | COPY year-service/. . 8 | 9 | RUN go build -o /out/year-service ./main.go 10 | 11 | FROM scratch AS bin 12 | WORKDIR /app 13 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 14 | COPY --from=build /out/year-service /app/ 15 | CMD ["/app/year-service"] 16 | -------------------------------------------------------------------------------- /golang/year-service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "math/rand" 8 | "net/http" 9 | "os" 10 | "time" 11 | 12 | _ "github.com/honeycombio/honeycomb-opentelemetry-go" 13 | 14 | "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" 15 | "go.opentelemetry.io/otel" 16 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace" 17 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" 18 | "go.opentelemetry.io/otel/propagation" 19 | sdktrace "go.opentelemetry.io/otel/sdk/trace" 20 | "go.opentelemetry.io/otel/trace" 21 | ) 22 | 23 | var ( 24 | tracer trace.Tracer 25 | ) 26 | 27 | func calculateYear() int { 28 | years := []int{2016, 2017, 2018, 2019, 2020} 29 | rand.Seed(time.Now().UnixNano()) 30 | time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond) 31 | return years[rand.Intn(len(years))] 32 | } 33 | 34 | func yearHandler(w http.ResponseWriter, r *http.Request) { 35 | ctx := r.Context() 36 | 37 | year := func(ctx context.Context) int { 38 | _, span := tracer.Start(ctx, "calculate-year") 39 | defer span.End() 40 | 41 | return calculateYear() 42 | }(ctx) 43 | 44 | _, _ = fmt.Fprintf(w, "%d", year) 45 | } 46 | 47 | func main() { 48 | ctx := context.Background() 49 | exporter, _ := otlptrace.New(ctx, otlptracegrpc.NewClient( 50 | otlptracegrpc.WithEndpoint(os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT")), 51 | otlptracegrpc.WithHeaders(map[string]string{ 52 | "x-honeycomb-team": os.Getenv("HONEYCOMB_API_KEY"), 53 | }), 54 | )) 55 | tracerProvider := sdktrace.NewTracerProvider( 56 | sdktrace.WithBatcher(exporter), 57 | ) 58 | defer tracerProvider.Shutdown(ctx) 59 | 60 | otel.SetTracerProvider(tracerProvider) 61 | otel.SetTextMapPropagator( 62 | propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}), 63 | ) 64 | 65 | tracer = otel.Tracer("greeting-service/year-service") 66 | 67 | mux := http.NewServeMux() 68 | mux.HandleFunc("/year", yearHandler) 69 | 70 | wrappedHandler := otelhttp.NewHandler(mux, "year") 71 | 72 | log.Println("Listening on http://localhost:6001/year") 73 | log.Fatal(http.ListenAndServe(":6001", wrappedHandler)) 74 | } 75 | -------------------------------------------------------------------------------- /java/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # When opening a file, EditorConfig plugins look for a file named .editorconfig 4 | # in the directory of the opened file and in every parent directory. 5 | # A search for .editorconfig files will stop if the root filepath is reached 6 | # or an EditorConfig file with root=true is found. 7 | 8 | # EditorConfig files are read top to bottom and the most recent rules found 9 | # take precedence. Properties from matching EditorConfig sections are applied 10 | # in the order they were read, so properties in closer files take precedence. 11 | 12 | # Java files 13 | [*.java] 14 | 15 | # indent style and size match root level rules -------------------------------------------------------------------------------- /java/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | agent/ 4 | build/ 5 | !gradle/wrapper/gradle-wrapper.jar 6 | !**/src/main/**/build/ 7 | !**/src/test/**/build/ 8 | 9 | ### STS ### 10 | .apt_generated 11 | .classpath 12 | .factorypath 13 | .project 14 | .settings 15 | .springBeans 16 | .sts4-cache 17 | bin/ 18 | !**/src/main/**/bin/ 19 | !**/src/test/**/bin/ 20 | 21 | ### IntelliJ IDEA ### 22 | .idea 23 | *.iws 24 | *.iml 25 | *.ipr 26 | out/ 27 | !**/src/main/**/out/ 28 | !**/src/test/**/out/ 29 | 30 | ### NetBeans ### 31 | /nbproject/private/ 32 | /nbbuild/ 33 | /dist/ 34 | /nbdist/ 35 | /.nb-gradle/ 36 | 37 | ### VS Code ### 38 | .vscode/ 39 | -------------------------------------------------------------------------------- /java/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gradle:8.1.0-jdk17 AS build 2 | WORKDIR /home/gradle/ 3 | RUN mkdir ./libs 4 | COPY --chown=gradle:gradle *.gradle ./ 5 | COPY --chown=gradle:gradle ./src ./src 6 | RUN gradle bootJar --no-daemon 7 | 8 | FROM eclipse-temurin:17-jdk 9 | VOLUME /tmp 10 | ENV JAVA_AGENT=otel-javaagent.jar 11 | ENV JAVA_TOOL_OPTIONS=-javaagent:${JAVA_AGENT} 12 | COPY --from=build /home/gradle/agent/${JAVA_AGENT} ./${JAVA_AGENT} 13 | COPY --from=build /home/gradle/build/libs/*.jar app.jar 14 | CMD ["java", "-jar", "app.jar"] 15 | -------------------------------------------------------------------------------- /java/frontend/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '3.2.3' 3 | id 'io.spring.dependency-management' version '1.1.4' 4 | id 'java' 5 | } 6 | 7 | group = 'io.honeycomb.examples' 8 | version = '0.0.1-SNAPSHOT' 9 | sourceCompatibility = '17' 10 | 11 | ext { 12 | otelApiVersion = '1.36.0' 13 | otelAgentVersion = '1.33.1' 14 | } 15 | 16 | configurations { 17 | agent 18 | } 19 | 20 | repositories { 21 | mavenCentral() 22 | mavenLocal() 23 | } 24 | 25 | dependencies { 26 | implementation("io.opentelemetry:opentelemetry-api:${otelApiVersion}") 27 | implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations:${otelAgentVersion}") 28 | 29 | agent "io.opentelemetry.javaagent:opentelemetry-javaagent:${otelAgentVersion}" 30 | 31 | implementation 'org.springframework.boot:spring-boot-starter-web' 32 | testImplementation('org.springframework.boot:spring-boot-starter-test') { 33 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' 34 | } 35 | } 36 | 37 | task copyAgentJar(type: Copy) { 38 | from configurations.agent { 39 | include '**/opentelemetry-javaagent*.jar' 40 | } 41 | into "agent" 42 | rename { fileName -> "otel-javaagent.jar" } 43 | } 44 | 45 | compileJava.dependsOn copyAgentJar 46 | bootRun.dependsOn copyAgentJar 47 | 48 | bootRun.doFirst { 49 | jvmArgs("-javaagent:agent/otel-javaagent.jar") 50 | } 51 | 52 | clean.doFirst { 53 | delete "agent" 54 | } 55 | 56 | test { 57 | useJUnitPlatform() 58 | } 59 | -------------------------------------------------------------------------------- /java/frontend/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honeycombio/example-greeting-service/6705c90566e5309e583e9c1312d87fe8a220cc1d/java/frontend/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /java/frontend/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /java/frontend/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'frontend-java' 2 | -------------------------------------------------------------------------------- /java/frontend/src/main/java/io/honeycomb/examples/frontend_java/FrontendApplication.java: -------------------------------------------------------------------------------- 1 | package io.honeycomb.examples.frontend_java; 2 | 3 | import io.opentelemetry.api.GlobalOpenTelemetry; 4 | import io.opentelemetry.api.trace.Tracer; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Bean; 8 | 9 | @SpringBootApplication 10 | public class FrontendApplication { 11 | 12 | @Bean 13 | public Tracer tracer() { 14 | return GlobalOpenTelemetry.getTracer("frontend-internal"); 15 | } 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(FrontendApplication.class, args); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /java/frontend/src/main/java/io/honeycomb/examples/frontend_java/GreetingController.java: -------------------------------------------------------------------------------- 1 | package io.honeycomb.examples.frontend_java; 2 | 3 | import io.opentelemetry.api.baggage.Baggage; 4 | import io.opentelemetry.api.trace.Span; 5 | import io.opentelemetry.api.trace.Tracer; 6 | import io.opentelemetry.context.Scope; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import java.net.URISyntaxException; 12 | 13 | @RestController 14 | public class GreetingController { 15 | @Autowired 16 | private NameService nameService; 17 | 18 | @Autowired 19 | private MessageService messageService; 20 | 21 | @Autowired 22 | private Tracer tracer; 23 | 24 | @RequestMapping("/greeting") 25 | public String index() throws URISyntaxException { 26 | String name = nameService.getName(); 27 | Span.current().setAttribute("app.username", name); 28 | 29 | try (final Scope ignored = Baggage.current() 30 | .toBuilder() 31 | .put("app.username", name) 32 | .build() 33 | .makeCurrent() 34 | ) { 35 | String message = messageService.getMessage(); 36 | 37 | Span span = tracer.spanBuilder("🎨 render message ✨").startSpan(); 38 | String greeting = String.format("Hello, %s, %s", name, message); 39 | span.end(); 40 | return greeting; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /java/frontend/src/main/java/io/honeycomb/examples/frontend_java/MessageService.java: -------------------------------------------------------------------------------- 1 | package io.honeycomb.examples.frontend_java; 2 | 3 | import java.io.IOException; 4 | import java.net.URI; 5 | import java.net.URISyntaxException; 6 | import java.net.http.HttpClient; 7 | import java.net.http.HttpRequest; 8 | import java.net.http.HttpResponse; 9 | 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Component; 12 | 13 | import io.opentelemetry.api.trace.Span; 14 | import io.opentelemetry.api.trace.Tracer; 15 | import io.opentelemetry.instrumentation.annotations.WithSpan; 16 | 17 | @Component 18 | public class MessageService { 19 | @Autowired 20 | private Tracer tracer; 21 | 22 | private String message_endpoint() { 23 | String messageEndpointFromEnv = "http://" + System.getenv().getOrDefault("MESSAGE_ENDPOINT", "localhost:9000"); 24 | return messageEndpointFromEnv + "/message"; 25 | } 26 | 27 | @WithSpan 28 | public String getMessage() throws URISyntaxException { 29 | URI message_uri = new URI(message_endpoint()); 30 | 31 | HttpClient client = HttpClient.newHttpClient(); 32 | HttpRequest request = HttpRequest.newBuilder() 33 | .uri(message_uri) 34 | .header("accept", "application/json") 35 | .build(); 36 | HttpResponse response = null; 37 | try { 38 | Span messageServiceCallSpan = tracer.spanBuilder("✨ call /message ✨").startSpan(); 39 | messageServiceCallSpan.makeCurrent(); 40 | response = client.send(request, HttpResponse.BodyHandlers.ofString()); 41 | messageServiceCallSpan.end(); 42 | } catch (IOException | InterruptedException e) { 43 | e.printStackTrace(); 44 | 45 | } 46 | return response.body(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /java/frontend/src/main/java/io/honeycomb/examples/frontend_java/NameService.java: -------------------------------------------------------------------------------- 1 | package io.honeycomb.examples.frontend_java; 2 | 3 | import java.io.IOException; 4 | import java.net.URI; 5 | import java.net.URISyntaxException; 6 | import java.net.http.HttpClient; 7 | import java.net.http.HttpRequest; 8 | import java.net.http.HttpResponse; 9 | 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Component; 12 | 13 | import io.opentelemetry.api.trace.Span; 14 | import io.opentelemetry.api.trace.Tracer; 15 | import io.opentelemetry.instrumentation.annotations.WithSpan; 16 | 17 | @Component 18 | public class NameService { 19 | @Autowired 20 | private Tracer tracer; 21 | 22 | private String name_endpoint() { 23 | String nameEndpointFromEnv = "http://" + System.getenv().getOrDefault("NAME_ENDPOINT", "localhost:8000"); 24 | return nameEndpointFromEnv + "/name"; 25 | } 26 | @WithSpan 27 | public String getName() throws URISyntaxException { 28 | URI name_uri = new URI(name_endpoint()); 29 | 30 | HttpClient client = HttpClient.newHttpClient(); 31 | HttpRequest request = HttpRequest.newBuilder() 32 | .uri(name_uri) 33 | .header("accept", "application/json") 34 | .build(); 35 | HttpResponse response = null; 36 | try { 37 | Span nameServiceCallSpan = tracer.spanBuilder("✨ call /name ✨").startSpan(); 38 | nameServiceCallSpan.makeCurrent(); 39 | response = client.send(request, HttpResponse.BodyHandlers.ofString()); 40 | nameServiceCallSpan.end(); 41 | } catch (IOException | InterruptedException e) { 42 | e.printStackTrace(); 43 | } 44 | return response.body(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /java/frontend/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=7777 2 | -------------------------------------------------------------------------------- /java/message-service/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | agent/ 4 | build/ 5 | !gradle/wrapper/gradle-wrapper.jar 6 | !**/src/main/**/build/ 7 | !**/src/test/**/build/ 8 | 9 | ### STS ### 10 | .apt_generated 11 | .classpath 12 | .factorypath 13 | .project 14 | .settings 15 | .springBeans 16 | .sts4-cache 17 | bin/ 18 | !**/src/main/**/bin/ 19 | !**/src/test/**/bin/ 20 | 21 | ### IntelliJ IDEA ### 22 | .idea 23 | *.iws 24 | *.iml 25 | *.ipr 26 | out/ 27 | !**/src/main/**/out/ 28 | !**/src/test/**/out/ 29 | 30 | ### NetBeans ### 31 | /nbproject/private/ 32 | /nbbuild/ 33 | /dist/ 34 | /nbdist/ 35 | /.nb-gradle/ 36 | 37 | ### VS Code ### 38 | .vscode/ 39 | -------------------------------------------------------------------------------- /java/message-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gradle:8.1.0-jdk17 AS build 2 | WORKDIR /home/gradle/ 3 | RUN mkdir ./libs 4 | COPY --chown=gradle:gradle *.gradle ./ 5 | COPY --chown=gradle:gradle ./src ./src 6 | RUN gradle bootJar --no-daemon 7 | 8 | FROM eclipse-temurin:17-jdk 9 | VOLUME /tmp 10 | ENV HONEY_JAVA_AGENT=honey-javaagent.jar 11 | ENV JAVA_TOOL_OPTIONS=-javaagent:${HONEY_JAVA_AGENT} 12 | COPY --from=build /home/gradle/agent/${HONEY_JAVA_AGENT} ./${HONEY_JAVA_AGENT} 13 | COPY --from=build /home/gradle/build/libs/*.jar app.jar 14 | CMD ["java", "-jar", "app.jar"] 15 | -------------------------------------------------------------------------------- /java/message-service/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '3.2.3' 3 | id 'io.spring.dependency-management' version '1.1.4' 4 | id 'java' 5 | } 6 | 7 | group = 'io.honeycomb.examples' 8 | version = '0.0.1-SNAPSHOT' 9 | sourceCompatibility = '17' 10 | 11 | ext { 12 | distroVersion = '1.6.0' 13 | } 14 | 15 | configurations { 16 | agent 17 | } 18 | 19 | repositories { 20 | mavenCentral() 21 | mavenLocal() 22 | } 23 | 24 | dependencies { 25 | agent "io.honeycomb:honeycomb-opentelemetry-javaagent:${distroVersion}" 26 | 27 | implementation("io.honeycomb:honeycomb-opentelemetry-sdk:${distroVersion}") 28 | implementation 'org.springframework.boot:spring-boot-starter-web' 29 | testImplementation('org.springframework.boot:spring-boot-starter-test') { 30 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' 31 | } 32 | } 33 | 34 | task copyAgentJar(type: Copy) { 35 | from configurations.agent { 36 | include '**/honeycomb-opentelemetry-javaagent*.jar' 37 | } 38 | into "agent" 39 | rename { fileName -> "honey-javaagent.jar" } 40 | } 41 | 42 | compileJava.dependsOn copyAgentJar 43 | bootRun.dependsOn copyAgentJar 44 | 45 | bootRun.doFirst { 46 | jvmArgs("-javaagent:agent/honey-javaagent.jar") 47 | } 48 | 49 | clean.doFirst { 50 | delete "agent" 51 | } 52 | 53 | test { 54 | useJUnitPlatform() 55 | } 56 | -------------------------------------------------------------------------------- /java/message-service/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honeycombio/example-greeting-service/6705c90566e5309e583e9c1312d87fe8a220cc1d/java/message-service/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /java/message-service/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /java/message-service/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'message-service-java' 2 | -------------------------------------------------------------------------------- /java/message-service/src/main/java/io/honeycomb/examples/message/MessageApplication.java: -------------------------------------------------------------------------------- 1 | package io.honeycomb.examples.message; 2 | 3 | import io.opentelemetry.api.GlobalOpenTelemetry; 4 | import io.opentelemetry.api.trace.Tracer; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Bean; 8 | 9 | @SpringBootApplication 10 | public class MessageApplication { 11 | @Bean 12 | public Tracer tracer() { 13 | return GlobalOpenTelemetry.getTracer("frontend-internal"); 14 | } 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.run(MessageApplication.class, args); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /java/message-service/src/main/java/io/honeycomb/examples/message/MessageEndpoints.java: -------------------------------------------------------------------------------- 1 | package io.honeycomb.examples.message; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.util.MimeTypeUtils; 5 | import org.springframework.web.bind.annotation.*; 6 | 7 | @RestController 8 | @RequestMapping(value = "message") 9 | public class MessageEndpoints { 10 | @Autowired 11 | private MessageService messageService; 12 | 13 | @GetMapping(produces = MimeTypeUtils.APPLICATION_JSON_VALUE) 14 | public String message() { 15 | return messageService.getMessage(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /java/message-service/src/main/java/io/honeycomb/examples/message/MessageService.java: -------------------------------------------------------------------------------- 1 | package io.honeycomb.examples.message; 2 | 3 | import java.util.Random; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | 8 | import io.opentelemetry.api.trace.Span; 9 | import io.opentelemetry.api.trace.Tracer; 10 | import io.opentelemetry.instrumentation.annotations.WithSpan; 11 | 12 | @Component 13 | public class MessageService { 14 | @Autowired 15 | private Tracer tracer; 16 | 17 | private static final String[] MESSAGES = new String[] { "how are you?", "how are you doing?", "what's good?", "what's up?", "how do you do?", 18 | "sup?", "good day to you", "how are things?", "howzit?", "woohoo" }; 19 | private static final Random generator = new Random(); 20 | 21 | @WithSpan 22 | public String getMessage() { 23 | return pickMessage(); 24 | } 25 | 26 | private String pickMessage() { 27 | Span messageLookupSpan = tracer.spanBuilder("📖 look up message ✨").startSpan(); 28 | messageLookupSpan.makeCurrent(); 29 | int rnd = generator.nextInt(MESSAGES.length); 30 | String message = MESSAGES[rnd]; 31 | messageLookupSpan.end(); 32 | return message; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /java/message-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=9000 2 | -------------------------------------------------------------------------------- /java/name-service/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | agent/ 4 | build/ 5 | !gradle/wrapper/gradle-wrapper.jar 6 | !**/src/main/**/build/ 7 | !**/src/test/**/build/ 8 | 9 | ### STS ### 10 | .apt_generated 11 | .classpath 12 | .factorypath 13 | .project 14 | .settings 15 | .springBeans 16 | .sts4-cache 17 | bin/ 18 | !**/src/main/**/bin/ 19 | !**/src/test/**/bin/ 20 | 21 | ### IntelliJ IDEA ### 22 | .idea 23 | *.iws 24 | *.iml 25 | *.ipr 26 | out/ 27 | !**/src/main/**/out/ 28 | !**/src/test/**/out/ 29 | 30 | ### NetBeans ### 31 | /nbproject/private/ 32 | /nbbuild/ 33 | /dist/ 34 | /nbdist/ 35 | /.nb-gradle/ 36 | 37 | ### VS Code ### 38 | .vscode/ 39 | -------------------------------------------------------------------------------- /java/name-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gradle:8.1.0-jdk17 AS build 2 | WORKDIR /home/gradle/ 3 | RUN mkdir ./libs 4 | COPY --chown=gradle:gradle *.gradle ./ 5 | COPY --chown=gradle:gradle ./src ./src 6 | RUN gradle bootJar --no-daemon 7 | 8 | FROM eclipse-temurin:17-jdk 9 | VOLUME /tmp 10 | ENV HONEY_JAVA_AGENT=honey-javaagent.jar 11 | ENV JAVA_TOOL_OPTIONS=-javaagent:${HONEY_JAVA_AGENT} 12 | COPY --from=build /home/gradle/agent/${HONEY_JAVA_AGENT} ./${HONEY_JAVA_AGENT} 13 | COPY --from=build /home/gradle/build/libs/*.jar app.jar 14 | CMD ["java", "-jar", "app.jar"] 15 | -------------------------------------------------------------------------------- /java/name-service/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '3.2.3' 3 | id 'io.spring.dependency-management' version '1.1.4' 4 | id 'java' 5 | } 6 | 7 | group = 'io.honeycomb.examples' 8 | version = '0.0.1-SNAPSHOT' 9 | sourceCompatibility = '17' 10 | 11 | ext { 12 | distroVersion = '1.6.0' 13 | } 14 | 15 | configurations { 16 | agent 17 | } 18 | 19 | repositories { 20 | mavenCentral() 21 | mavenLocal() 22 | } 23 | 24 | dependencies { 25 | agent "io.honeycomb:honeycomb-opentelemetry-javaagent:${distroVersion}" 26 | 27 | implementation("io.honeycomb:honeycomb-opentelemetry-sdk:${distroVersion}") 28 | implementation 'org.springframework.boot:spring-boot-starter-web' 29 | testImplementation('org.springframework.boot:spring-boot-starter-test') { 30 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' 31 | } 32 | } 33 | 34 | task copyAgentJar(type: Copy) { 35 | from configurations.agent { 36 | include '**/honeycomb-opentelemetry-javaagent*.jar' 37 | } 38 | into "agent" 39 | rename { fileName -> "honey-javaagent.jar" } 40 | } 41 | 42 | compileJava.dependsOn copyAgentJar 43 | bootRun.dependsOn copyAgentJar 44 | 45 | bootRun.doFirst { 46 | jvmArgs("-javaagent:agent/honey-javaagent.jar") 47 | } 48 | 49 | clean.doFirst { 50 | delete "agent" 51 | } 52 | 53 | test { 54 | useJUnitPlatform() 55 | } 56 | -------------------------------------------------------------------------------- /java/name-service/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honeycombio/example-greeting-service/6705c90566e5309e583e9c1312d87fe8a220cc1d/java/name-service/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /java/name-service/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /java/name-service/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'name-java' 2 | -------------------------------------------------------------------------------- /java/name-service/src/main/java/io/honeycomb/examples/name/MessageService.java: -------------------------------------------------------------------------------- 1 | package io.honeycomb.examples.name; 2 | 3 | import java.io.IOException; 4 | import java.net.URI; 5 | import java.net.URISyntaxException; 6 | import java.net.http.HttpClient; 7 | import java.net.http.HttpRequest; 8 | import java.net.http.HttpResponse; 9 | 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Component; 12 | 13 | import io.opentelemetry.api.trace.Span; 14 | import io.opentelemetry.api.trace.Tracer; 15 | import io.opentelemetry.instrumentation.annotations.WithSpan; 16 | 17 | @Component 18 | public class MessageService { 19 | @Autowired 20 | private Tracer tracer; 21 | 22 | private String message_endpoint = "http://localhost:9000/message"; 23 | 24 | @WithSpan 25 | public String getMessage() throws URISyntaxException { 26 | URI message_uri = new URI(message_endpoint); 27 | 28 | HttpClient client = HttpClient.newHttpClient(); 29 | HttpRequest request = HttpRequest.newBuilder() 30 | .uri(message_uri) 31 | .header("accept", "application/json") 32 | .build(); 33 | HttpResponse response = null; 34 | try { 35 | Span messageServiceCallSpan = tracer.spanBuilder("✨ call /name ✨").startSpan(); 36 | messageServiceCallSpan.makeCurrent(); 37 | response = client.send(request, HttpResponse.BodyHandlers.ofString()); 38 | messageServiceCallSpan.end(); 39 | } catch (IOException | InterruptedException e) { 40 | e.printStackTrace(); 41 | 42 | } 43 | return response.body(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /java/name-service/src/main/java/io/honeycomb/examples/name/NameApplication.java: -------------------------------------------------------------------------------- 1 | package io.honeycomb.examples.name; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.Bean; 6 | 7 | import io.opentelemetry.api.GlobalOpenTelemetry; 8 | import io.opentelemetry.api.trace.Tracer; 9 | 10 | @SpringBootApplication 11 | public class NameApplication { 12 | 13 | @Bean 14 | public Tracer tracer() { 15 | return GlobalOpenTelemetry.getTracer("frontend-internal"); 16 | } 17 | 18 | public static void main(String[] args) { 19 | SpringApplication.run(NameApplication.class, args); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /java/name-service/src/main/java/io/honeycomb/examples/name/NameController.java: -------------------------------------------------------------------------------- 1 | package io.honeycomb.examples.name; 2 | 3 | import java.io.IOException; 4 | import java.net.URISyntaxException; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | public class NameController { 12 | @Autowired 13 | private NameService nameService; 14 | 15 | @RequestMapping("/name") 16 | public String index() throws URISyntaxException, IOException, InterruptedException { 17 | return nameService.getName(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /java/name-service/src/main/java/io/honeycomb/examples/name/NameService.java: -------------------------------------------------------------------------------- 1 | package io.honeycomb.examples.name; 2 | 3 | import java.io.IOException; 4 | import java.net.URISyntaxException; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Random; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Component; 11 | 12 | import io.opentelemetry.api.trace.Span; 13 | import io.opentelemetry.api.trace.Tracer; 14 | import io.opentelemetry.instrumentation.annotations.WithSpan; 15 | 16 | @Component 17 | public class NameService { 18 | @Autowired 19 | private Tracer tracer; 20 | 21 | private static final Map namesByYear = new HashMap<>() {{ 22 | this.put(2015, new String[]{"sophia", "jackson", "emma", "aiden", "olivia", "liam", "ava", "lucas", "mia", "noah"}); 23 | this.put(2016, new String[]{"sophia", "jackson", "emma", "aiden", "olivia", "lucas", "ava", "liam", "mia", "noah"}); 24 | this.put(2017, new String[]{"sophia", "jackson", "olivia", "liam", "emma", "noah", "ava", "aiden", "isabella", "lucas"}); 25 | this.put(2018, new String[]{"sophia", "jackson", "olivia", "liam", "emma", "noah", "ava", "aiden", "isabella", "caden"}); 26 | this.put(2019, new String[]{"sophia", "liam", "olivia", "jackson", "emma", "noah", "ava", "aiden", "aira", "grayson"}); 27 | this.put(2020, new String[]{"olivia", "noah", "emma", "liam", "ava", "elijah", "isabella", "oliver", "sophia", "lucas"}); 28 | }}; 29 | 30 | private static final Random generator = new Random(); 31 | 32 | @Autowired 33 | YearService yearService; 34 | 35 | @WithSpan 36 | public String getName() throws NumberFormatException, URISyntaxException, IOException, InterruptedException { 37 | int year = Integer.parseInt(yearService.getYear()); 38 | 39 | Span nameLookupSpan = tracer.spanBuilder("📖 look up name based on year ✨").startSpan(); 40 | nameLookupSpan.makeCurrent(); 41 | String[] candidateNames = namesByYear.get(year); 42 | int rnd = generator.nextInt(candidateNames.length); 43 | String name = candidateNames[rnd]; 44 | nameLookupSpan.end(); 45 | 46 | return name; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /java/name-service/src/main/java/io/honeycomb/examples/name/YearService.java: -------------------------------------------------------------------------------- 1 | package io.honeycomb.examples.name; 2 | 3 | import java.io.IOException; 4 | import java.net.URI; 5 | import java.net.URISyntaxException; 6 | import java.net.http.HttpClient; 7 | import java.net.http.HttpRequest; 8 | import java.net.http.HttpResponse; 9 | 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Component; 12 | 13 | import io.opentelemetry.api.trace.Span; 14 | import io.opentelemetry.api.trace.Tracer; 15 | import io.opentelemetry.instrumentation.annotations.WithSpan; 16 | 17 | @Component 18 | public class YearService { 19 | @Autowired 20 | private Tracer tracer; 21 | 22 | private String year_endpoint() { 23 | String yearEndpointFromEnv = "http://" + System.getenv().getOrDefault("YEAR_ENDPOINT", "localhost:6001"); 24 | return yearEndpointFromEnv + "/year"; 25 | } 26 | 27 | @WithSpan 28 | public String getYear() throws URISyntaxException { 29 | URI yearUri = new URI(year_endpoint()); 30 | 31 | HttpClient client = HttpClient.newHttpClient(); 32 | HttpRequest request = HttpRequest.newBuilder() 33 | .uri(yearUri) 34 | .header("accept", "application/json") 35 | .build(); 36 | HttpResponse response = null; 37 | Span yearServiceCallSpan = tracer.spanBuilder("✨ call /year ✨").startSpan(); 38 | yearServiceCallSpan.makeCurrent(); 39 | try { 40 | response = client.send(request, HttpResponse.BodyHandlers.ofString()); 41 | } catch (IOException | InterruptedException e) { 42 | e.printStackTrace(); 43 | } finally { 44 | yearServiceCallSpan.end(); 45 | } 46 | return response == null 47 | ? "" 48 | : response.body(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /java/name-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8000 2 | -------------------------------------------------------------------------------- /java/year-service/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | agent/ 4 | build/ 5 | !gradle/wrapper/gradle-wrapper.jar 6 | !**/src/main/**/build/ 7 | !**/src/test/**/build/ 8 | 9 | ### STS ### 10 | .apt_generated 11 | .classpath 12 | .factorypath 13 | .project 14 | .settings 15 | .springBeans 16 | .sts4-cache 17 | bin/ 18 | !**/src/main/**/bin/ 19 | !**/src/test/**/bin/ 20 | 21 | ### IntelliJ IDEA ### 22 | .idea 23 | *.iws 24 | *.iml 25 | *.ipr 26 | out/ 27 | !**/src/main/**/out/ 28 | !**/src/test/**/out/ 29 | 30 | ### NetBeans ### 31 | /nbproject/private/ 32 | /nbbuild/ 33 | /dist/ 34 | /nbdist/ 35 | /.nb-gradle/ 36 | 37 | ### VS Code ### 38 | .vscode/ 39 | -------------------------------------------------------------------------------- /java/year-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gradle:8.1.0-jdk17 AS build 2 | WORKDIR /home/gradle/ 3 | RUN mkdir ./libs 4 | COPY --chown=gradle:gradle *.gradle ./ 5 | COPY --chown=gradle:gradle ./src ./src 6 | RUN gradle bootJar --no-daemon 7 | 8 | FROM eclipse-temurin:17-jdk 9 | VOLUME /tmp 10 | ENV JAVA_AGENT=otel-javaagent.jar 11 | ENV JAVA_TOOL_OPTIONS=-javaagent:${JAVA_AGENT} 12 | COPY --from=build /home/gradle/agent/${JAVA_AGENT} ./${JAVA_AGENT} 13 | COPY --from=build /home/gradle/build/libs/*.jar app.jar 14 | CMD ["java", "-jar", "app.jar"] 15 | -------------------------------------------------------------------------------- /java/year-service/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '3.2.3' 3 | id 'io.spring.dependency-management' version '1.1.4' 4 | id 'java' 5 | } 6 | 7 | group = 'io.honeycomb.examples' 8 | version = '0.0.1-SNAPSHOT' 9 | sourceCompatibility = '17' 10 | 11 | ext { 12 | otelAlphaVersion = '1.33.1-alpha' 13 | otelApiVersion = '1.36.0' 14 | otelAgentVersion = '1.33.1' 15 | } 16 | 17 | configurations { 18 | agent 19 | } 20 | 21 | repositories { 22 | mavenCentral() 23 | mavenLocal() 24 | } 25 | 26 | dependencies { 27 | implementation("org.apache.logging.log4j:log4j-api:2.17.2") 28 | implementation("org.apache.logging.log4j:log4j-core:2.17.2") 29 | 30 | implementation("io.opentelemetry:opentelemetry-api:${otelApiVersion}") 31 | implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations:${otelAgentVersion}") 32 | 33 | agent "io.opentelemetry.javaagent:opentelemetry-javaagent:${otelAgentVersion}" 34 | 35 | implementation 'org.springframework.boot:spring-boot-starter-web' 36 | testImplementation('org.springframework.boot:spring-boot-starter-test') { 37 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' 38 | } 39 | } 40 | 41 | task copyAgentJar(type: Copy) { 42 | from configurations.agent { 43 | include '**/opentelemetry-javaagent*.jar' 44 | } 45 | into "agent" 46 | rename { fileName -> "otel-javaagent.jar" } 47 | } 48 | 49 | compileJava.dependsOn copyAgentJar 50 | bootRun.dependsOn copyAgentJar 51 | 52 | bootRun.doFirst { 53 | jvmArgs("-javaagent:agent/otel-javaagent.jar") 54 | } 55 | 56 | clean.doFirst { 57 | delete "agent" 58 | } 59 | 60 | test { 61 | useJUnitPlatform() 62 | } 63 | -------------------------------------------------------------------------------- /java/year-service/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honeycombio/example-greeting-service/6705c90566e5309e583e9c1312d87fe8a220cc1d/java/year-service/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /java/year-service/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /java/year-service/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'year-service-java' 2 | -------------------------------------------------------------------------------- /java/year-service/src/main/java/io/honeycomb/examples/javaotlp/YearApplication.java: -------------------------------------------------------------------------------- 1 | package io.honeycomb.examples.javaotlp; 2 | 3 | import io.opentelemetry.api.OpenTelemetry; 4 | import io.opentelemetry.api.GlobalOpenTelemetry; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Bean; 8 | 9 | @SpringBootApplication 10 | public class YearApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(YearApplication.class, args); 14 | } 15 | 16 | @Bean 17 | public OpenTelemetry openTelemetry() { 18 | return GlobalOpenTelemetry.get(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /java/year-service/src/main/java/io/honeycomb/examples/javaotlp/YearController.java: -------------------------------------------------------------------------------- 1 | package io.honeycomb.examples.javaotlp; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RestController 8 | public class YearController { 9 | @Autowired 10 | private YearService yearService; 11 | 12 | @RequestMapping("/year") 13 | public String index() { 14 | return yearService.getYear(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /java/year-service/src/main/java/io/honeycomb/examples/javaotlp/YearService.java: -------------------------------------------------------------------------------- 1 | package io.honeycomb.examples.javaotlp; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Random; 6 | import org.apache.logging.log4j.Logger; 7 | import org.apache.logging.log4j.LogManager; 8 | import org.apache.logging.log4j.message.ObjectMessage; 9 | import org.springframework.stereotype.Component; 10 | import io.opentelemetry.instrumentation.annotations.WithSpan; 11 | import io.opentelemetry.api.OpenTelemetry; 12 | import io.opentelemetry.api.trace.Span; 13 | import io.opentelemetry.api.trace.Tracer; 14 | 15 | @Component 16 | public class YearService { 17 | private static final String[] YEARS = new String[] { "2015", "2016", "2017", "2018", "2019", "2020" }; 18 | private static final Random generator = new Random(); 19 | 20 | private Tracer tracer; 21 | private static final Logger logger = LogManager.getLogger("my-logger"); 22 | 23 | public YearService(OpenTelemetry openTelemetry) { 24 | tracer = openTelemetry.getTracer("year-tracer"); 25 | } 26 | 27 | @WithSpan 28 | public String getYear() { 29 | Span span = tracer.spanBuilder("🗓 get-a-year ✨").startSpan(); 30 | int rnd = generator.nextInt(YEARS.length); 31 | String year = YEARS[rnd]; 32 | Map mapMessage = new HashMap<>(); 33 | mapMessage.put("selected_year", year); 34 | logger.info(new ObjectMessage(mapMessage)); 35 | span.end(); 36 | 37 | return year; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /java/year-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=6001 2 | -------------------------------------------------------------------------------- /java/year-service/src/test/java/io/honeycomb/examples/javaotlp/YearApplicationTests.java: -------------------------------------------------------------------------------- 1 | package io.honeycomb.examples.javaotlp; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class YearApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /node/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # When opening a file, EditorConfig plugins look for a file named .editorconfig 4 | # in the directory of the opened file and in every parent directory. 5 | # A search for .editorconfig files will stop if the root filepath is reached 6 | # or an EditorConfig file with root=true is found. 7 | 8 | # EditorConfig files are read top to bottom and the most recent rules found 9 | # take precedence. Properties from matching EditorConfig sections are applied 10 | # in the order they were read, so properties in closer files take precedence. 11 | 12 | # JavaScript, TypeScript files 13 | [*.js, *.ts] 14 | 15 | indent_size = 2 16 | insert_final_newline = true 17 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /node/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tilt_config.json -------------------------------------------------------------------------------- /node/.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 18.16.0 2 | -------------------------------------------------------------------------------- /node/README.md: -------------------------------------------------------------------------------- 1 | # Node Greeting Services 2 | 3 | ## Notes 4 | 5 | - Frontend, message, and name services use beelines 6 | - Year service uses vanilla OTel 7 | 8 | ## How to run 9 | 10 | **Required**: set environment variables 11 | 12 | - HONEYCOMB_API_KEY 13 | - HONEYCOMB_DATASET 14 | 15 | ### Install Node 16 | If you're using option 1 or 3 listed below, you will have to install node on your machine. The preferred way to install node is with [asdf](https://asdf-vm.com/guide/getting-started.html#_1-install-dependencies). 17 | 18 | Once you have asdf set up, run the following to install node. Check the version needed in the `.tool-versions` file in the root of this repo. 19 | 20 | ``` 21 | asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git 22 | ``` 23 | 24 | ``` 25 | asdf install nodejs 26 | ``` 27 | 28 | ### Option 1: One service at a time 29 | 30 | - In each individual service directory, run `npm start` 31 | 32 | ### Option 2: All Node Services via Docker 33 | 34 | - In Node directory, run `docker-compose up --build` 35 | 36 | ### Option 3: All Node Services via Tilt 37 | 38 | In top-level directory run `tilt up node` 39 | 40 | ## See it in action 41 | 42 | `curl localhost:7777/greeting` for greeting 43 | 44 | `curl localhost:9000/message` for message only 45 | 46 | `curl localhost:8000/name` for name only 47 | 48 | `curl localhost:6001` for year only 49 | -------------------------------------------------------------------------------- /node/frontend-service/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /node/frontend-service/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'es5', 3 | tabWidth: 2, 4 | singleQuote: true, 5 | printWidth: 100, 6 | }; 7 | -------------------------------------------------------------------------------- /node/frontend-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 8 | # where available (npm@5+) 9 | COPY package*.json ./ 10 | 11 | RUN npm install 12 | # If you are building your code for production 13 | # RUN npm ci --only=production 14 | 15 | # Bundle app source 16 | COPY . . 17 | 18 | EXPOSE 7777 19 | CMD [ "npm", "start" ] 20 | -------------------------------------------------------------------------------- /node/frontend-service/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const opentelemetry = require('@opentelemetry/api'); 3 | 4 | const express = require('express'); 5 | const fetch = require('node-fetch'); 6 | const cors = require('cors'); 7 | 8 | // Constants 9 | const PORT = 7777; 10 | const HOST = '0.0.0.0'; 11 | const MESSAGE_ENDPOINT = process.env.MESSAGE_ENDPOINT || 'localhost:9000'; 12 | const NAME_ENDPOINT = process.env.NAME_ENDPOINT || 'localhost:8000'; 13 | 14 | console.log(NAME_ENDPOINT) 15 | const nameUrl = `${NAME_ENDPOINT}/name`; 16 | const messageUrl = `${MESSAGE_ENDPOINT}/message`; 17 | 18 | // App 19 | const app = express(); 20 | 21 | // CORS for use with web frontend 22 | const corsOptions = { 23 | origin: ['http://localhost:8080'], 24 | optionsSuccessStatus: 200 25 | }; 26 | 27 | app.use(cors(corsOptions)) 28 | 29 | const tracer = opentelemetry.trace.getTracer( 30 | 'default' 31 | ); 32 | 33 | app.get('/greeting', async (req, res) => { 34 | const greetingSpan = tracer.startSpan('✨ preparing greeting ✨'); 35 | greetingSpan.end() 36 | 37 | const nameSpan = tracer.startSpan('✨ call /name ✨'); 38 | const name = await getName(nameUrl); 39 | nameSpan.setAttribute("app.user_name", name); 40 | nameSpan.end() 41 | 42 | const messageSpan = tracer.startSpan('✨ call /message ✨'); 43 | const message = await getMessage(messageUrl); 44 | messageSpan.setAttribute("app.user_message", message); 45 | messageSpan.end() 46 | 47 | const responseSpan = tracer.startSpan('✨ post response ✨'); 48 | res.send(`Hello ${name}, ${message}`); 49 | responseSpan.end() 50 | }); 51 | 52 | const getName = (url) => 53 | fetch(url) 54 | .then((data) => { 55 | return data.text(); 56 | }) 57 | .then((text) => { 58 | console.log(text); 59 | return text; 60 | }) 61 | .catch((err) => console.error(`Problem getting name: ${err}`)); 62 | 63 | const getMessage = (url) => 64 | fetch(url) 65 | .then((data) => { 66 | return data.text(); 67 | }) 68 | .then((text) => { 69 | console.log(text); 70 | return text; 71 | }) 72 | .catch((err) => console.error(`Problem getting message: ${err}`)); 73 | 74 | app.listen(PORT, HOST); 75 | console.log(`Running node frontend service on http://${HOST}:${PORT}`); 76 | -------------------------------------------------------------------------------- /node/frontend-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "nodemon main.js", 8 | "start": "node -r ./tracing.js main.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@honeycombio/opentelemetry-node": "^0.7.0", 15 | "@opentelemetry/api": "^1.4.1", 16 | "@opentelemetry/auto-instrumentations-node": "^0.42.0", 17 | "cors": "^2.8.5", 18 | "express": "^4.18.3", 19 | "node-fetch": "^2.6.1" 20 | }, 21 | "devDependencies": { 22 | "nodemon": "^3.0.1", 23 | "prettier": "^3.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /node/frontend-service/tracing.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { HoneycombSDK } = require('@honeycombio/opentelemetry-node'); 4 | const { 5 | getNodeAutoInstrumentations, 6 | } = require('@opentelemetry/auto-instrumentations-node'); 7 | 8 | const sdk = new HoneycombSDK({ 9 | instrumentations: [getNodeAutoInstrumentations({ 10 | '@opentelemetry/instrumentation-fs': { 11 | enabled: false, 12 | }, 13 | })], 14 | serviceName: "frontend-node", 15 | }); 16 | 17 | sdk.start() 18 | -------------------------------------------------------------------------------- /node/message-service/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /node/message-service/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'es5', 3 | tabWidth: 2, 4 | singleQuote: true, 5 | printWidth: 100, 6 | }; 7 | -------------------------------------------------------------------------------- /node/message-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 8 | # where available (npm@5+) 9 | COPY package*.json ./ 10 | 11 | RUN npm install 12 | # If you are building your code for production 13 | # RUN npm ci --only=production 14 | 15 | # Bundle app source 16 | COPY . . 17 | 18 | EXPOSE 9000 19 | CMD [ "node", "main.js" ] 20 | -------------------------------------------------------------------------------- /node/message-service/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const beeline = require('honeycomb-beeline'); 3 | 4 | beeline({ 5 | // Get this via https://ui.honeycomb.io/account after signing up for Honeycomb 6 | writeKey: process.env.HONEYCOMB_API_KEY, 7 | // The name of your app is a good choice to start with 8 | dataset: process.env.HONEYCOMB_DATASET, 9 | serviceName: process.env.SERVICE_NAME || 'node-message-service', 10 | apiHost: process.env.HONEYCOMB_API_ENDPOINT || 'https://api.honeycomb.io', 11 | httpTraceParserHook: beeline.w3c.httpTraceParserHook, 12 | httpTracePropagationHook: beeline.w3c.httpTracePropagationHook, 13 | }); 14 | 15 | const express = require('express'); 16 | 17 | // Constants 18 | const PORT = 9000; 19 | const HOST = '0.0.0.0'; 20 | 21 | // App 22 | const app = express(); 23 | app.get('/message', async (req, res) => { 24 | beeline.addContext({ name: 'Message' }); 25 | const messageSpan = beeline.startSpan({ name: 'look up message' }); 26 | const message = await determineMessage(messages); 27 | beeline.addTraceContext({ user_message: message }); 28 | beeline.finishSpan(messageSpan); 29 | res.send(`${message}`); 30 | }); 31 | 32 | const messages = [ 33 | 'how are you?', 34 | 'how are you doing?', 35 | "what's good?", 36 | "what's up?", 37 | 'how do you do?', 38 | 'sup?', 39 | 'good day to you', 40 | 'how are things?', 41 | 'howzit?', 42 | 'woohoo', 43 | ]; 44 | 45 | function determineMessage() { 46 | return messages[Math.floor(Math.random() * messages.length)]; 47 | } 48 | 49 | app.listen(PORT, HOST); 50 | console.log(`Running node message service on http://${HOST}:${PORT}`); 51 | -------------------------------------------------------------------------------- /node/message-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "message-service", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "nodemon main.js", 8 | "start": "node main.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "express": "^4.18.3", 15 | "honeycomb-beeline": "^4.1.0" 16 | }, 17 | "devDependencies": { 18 | "nodemon": "^3.0.1", 19 | "prettier": "^3.0.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /node/name-service/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /node/name-service/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'es5', 3 | tabWidth: 2, 4 | singleQuote: true, 5 | printWidth: 100, 6 | }; 7 | -------------------------------------------------------------------------------- /node/name-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 8 | # where available (npm@5+) 9 | COPY package*.json ./ 10 | 11 | RUN npm install 12 | # If you are building your code for production 13 | # RUN npm ci --only=production 14 | 15 | # Bundle app source 16 | COPY . . 17 | 18 | EXPOSE 8000 19 | CMD [ "node", "main.js" ] 20 | -------------------------------------------------------------------------------- /node/name-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "name-service", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "nodemon main.js", 8 | "start": "node main.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "express": "^4.18.3", 15 | "honeycomb-beeline": "^4.1.0", 16 | "node-fetch": "^2.6.12" 17 | }, 18 | "devDependencies": { 19 | "nodemon": "^3.0.1", 20 | "prettier": "^3.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /node/year-service/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /node/year-service/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'es5', 3 | tabWidth: 2, 4 | singleQuote: true, 5 | printWidth: 100, 6 | }; 7 | -------------------------------------------------------------------------------- /node/year-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 8 | # where available (npm@5+) 9 | COPY package*.json ./ 10 | 11 | RUN npm install 12 | # If you are building your code for production 13 | # RUN npm ci --only=production 14 | 15 | # Bundle app source 16 | COPY . . 17 | 18 | EXPOSE 6001 19 | CMD [ "npm", "start" ] 20 | -------------------------------------------------------------------------------- /node/year-service/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const opentelemetry = require('@opentelemetry/api'); 4 | const bunyan = require('bunyan'); 5 | 6 | const express = require('express'); 7 | const logger = bunyan.createLogger({name: 'myapp', level: 'info'}); 8 | 9 | // Constants 10 | const PORT = 6001; 11 | const HOST = '0.0.0.0'; 12 | 13 | // App 14 | const app = express(); 15 | app.get('/year', async (req, res) => { 16 | const span = opentelemetry.trace.getTracer('default').startSpan('Getting year'); 17 | const year = await determineYear(years); 18 | logger.info({"selected_year":year}); 19 | res.send(`${year}`); 20 | span.end(); 21 | }); 22 | 23 | const years = [2015, 2016, 2017, 2018, 2019, 2020]; 24 | 25 | function determineYear() { 26 | return years[Math.floor(Math.random() * years.length)]; 27 | } 28 | 29 | app.listen(PORT, HOST); 30 | console.log(`Running node year service on http://${HOST}:${PORT}`); 31 | -------------------------------------------------------------------------------- /node/year-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "year-service", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "nodemon main.js", 8 | "start": "node -r ./tracing.js main.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@opentelemetry/auto-instrumentations-node": "^0.42.0", 15 | "@opentelemetry/exporter-logs-otlp-grpc": "^0.49.1", 16 | "@opentelemetry/exporter-trace-otlp-grpc": "^0.49.1", 17 | "@opentelemetry/sdk-node": "^0.49.1", 18 | "bunyan": "^1.8.15", 19 | "express": "^4.18.3" 20 | }, 21 | "devDependencies": { 22 | "nodemon": "^3.0.1", 23 | "prettier": "^3.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /node/year-service/tracing.js: -------------------------------------------------------------------------------- 1 | const process = require('process'); 2 | 3 | const { NodeSDK, logs, tracing } = require('@opentelemetry/sdk-node'); 4 | const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node'); 5 | const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc'); 6 | const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-grpc'); 7 | 8 | const sdk = new NodeSDK({ 9 | spanProcessor: new tracing.SimpleSpanProcessor(new OTLPTraceExporter()), 10 | logRecordProcessor: new logs.SimpleLogRecordProcessor(new OTLPLogExporter()), 11 | instrumentations: [getNodeAutoInstrumentations({ 12 | '@opentelemetry/instrumentation-fs': { 13 | enabled: false, 14 | }, 15 | })], 16 | }); 17 | 18 | sdk.start(); 19 | 20 | process.on('SIGTERM', () => { 21 | sdk 22 | .shutdown() 23 | .then(() => console.log('Tracing terminated')) 24 | .catch((error) => console.log('Error terminating tracing', error)) 25 | .finally(() => process.exit(0)); 26 | }); 27 | -------------------------------------------------------------------------------- /otel-collector-config.yaml: -------------------------------------------------------------------------------- 1 | receivers: 2 | otlp: 3 | protocols: 4 | grpc: 5 | endpoint: 0.0.0.0:55680 6 | http: 7 | endpoint: 0.0.0.0:55681 8 | # CORS for browser communication 9 | cors: 10 | allowed_origins: "http://localhost:8080" 11 | 12 | 13 | processors: 14 | batch: 15 | 16 | exporters: 17 | logging: 18 | loglevel: debug 19 | otlp/hny: 20 | endpoint: api.honeycomb.io:443 21 | headers: 22 | # use environment variables to set the values for these headers 23 | "x-honeycomb-team": "${HONEYCOMB_API_KEY}" 24 | # "x-honeycomb-dataset": "${HONEYCOMB_DATASET}" for classic only 25 | # file: # optionally export data to a file 26 | # path: ./data.json # optional file to store exported data 27 | service: 28 | pipelines: 29 | traces: 30 | receivers: [otlp] 31 | processors: [batch] 32 | exporters: [otlp/hny, logging] 33 | logs: 34 | receivers: [otlp] 35 | processors: [batch] 36 | exporters: [otlp/hny, logging] 37 | # exporters: [otlp/hny, logging, file] 38 | -------------------------------------------------------------------------------- /python/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # When opening a file, EditorConfig plugins look for a file named .editorconfig 4 | # in the directory of the opened file and in every parent directory. 5 | # A search for .editorconfig files will stop if the root filepath is reached 6 | # or an EditorConfig file with root=true is found. 7 | 8 | # EditorConfig files are read top to bottom and the most recent rules found 9 | # take precedence. Properties from matching EditorConfig sections are applied 10 | # in the order they were read, so properties in closer files take precedence. 11 | 12 | # Python files 13 | [*.py] 14 | 15 | insert_final_newline = true 16 | trim_trailing_whitespace = true 17 | end_of_line = lf 18 | charset = utf-8 19 | 20 | # https://www.python.org/dev/peps/pep-0008/#id19 21 | max_line_length = 79 -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.egg-info -------------------------------------------------------------------------------- /python/.tool-versions: -------------------------------------------------------------------------------- 1 | poetry 1.3.2 2 | python 3.10.10 3 | -------------------------------------------------------------------------------- /python/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim 2 | 3 | WORKDIR /app 4 | 5 | ENV PYTHONFAULTHANDLER=1 \ 6 | PYTHONUNBUFFERED=1 \ 7 | PYTHONHASHSEED=random \ 8 | PIP_NO_CACHE_DIR=off \ 9 | PIP_DISABLE_PIP_VERSION_CHECK=on \ 10 | PIP_DEFAULT_TIMEOUT=100 \ 11 | POETRY_VERSION=1.3.2 12 | 13 | RUN pip install "poetry==$POETRY_VERSION" 14 | 15 | COPY poetry.lock pyproject.toml ./ 16 | RUN poetry config virtualenvs.create false \ 17 | && poetry install --no-root --no-interaction --no-ansi 18 | 19 | COPY ./frontend ./frontend 20 | 21 | EXPOSE 7777 22 | CMD ["poetry", "run", "python", "-m", "frontend"] 23 | -------------------------------------------------------------------------------- /python/frontend/README.rst: -------------------------------------------------------------------------------- 1 | frontend service implemented using `werkzeug` and `urllib` middleware. -------------------------------------------------------------------------------- /python/frontend/frontend/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.0' 2 | 3 | import os 4 | 5 | from werkzeug.routing import Map, Rule 6 | from werkzeug.wrappers import Request, Response 7 | from http.client import HTTPException 8 | 9 | from beeline.patch import urllib 10 | import urllib 11 | 12 | 13 | class Greeting(object): 14 | NAME_ENDPOINT = os.environ.get('NAME_ENDPOINT', 'http://localhost:8000') + '/name' 15 | MESSAGE_ENDPOINT = os.environ.get('MESSAGE_ENDPOINT', 'http://localhost:9000') + '/message' 16 | def __init__(self): 17 | self.url_map = Map([ 18 | Rule('/greeting', endpoint='greeting'), 19 | ]) 20 | 21 | def on_greeting(self, request): 22 | name = self.get_name() 23 | message = self.get_message() 24 | return Response('Hello %s, %s' % (name, message), mimetype='text/plain') 25 | 26 | def dispatch_request(self, request): 27 | adapter = self.url_map.bind_to_environ(request.environ) 28 | try: 29 | endpoint, values = adapter.match() 30 | return getattr(self, 'on_' + endpoint)(request, **values) 31 | except HTTPException as e: 32 | return e 33 | 34 | def get_name(self): 35 | with urllib.request.urlopen(self.NAME_ENDPOINT) as f: 36 | return f.read().decode('utf-8') 37 | 38 | def get_message(self): 39 | with urllib.request.urlopen(self.MESSAGE_ENDPOINT) as f: 40 | return f.read().decode('utf-8') 41 | 42 | def wsgi_app(self, environ, start_response): 43 | request = Request(environ) 44 | response = self.dispatch_request(request) 45 | return response(environ, start_response) 46 | 47 | def __call__(self, environ, start_response): 48 | return self.wsgi_app(environ, start_response) 49 | 50 | 51 | def create_app(): 52 | app = Greeting() 53 | return app 54 | -------------------------------------------------------------------------------- /python/frontend/frontend/__main__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from frontend import create_app 3 | from werkzeug.serving import run_simple 4 | 5 | import beeline 6 | from beeline.middleware.werkzeug import HoneyWSGIMiddleware 7 | import beeline.propagation.w3c as w3c 8 | 9 | beeline.init( 10 | # Get this via https://ui.honeycomb.io/account after signing up for Honeycomb 11 | writekey=os.environ.get("HONEYCOMB_API_KEY"), 12 | # The name of your app is a good choice to start with 13 | api_host=os.environ.get("HONEYCOMB_API_ENDPOINT", 14 | "https://api.honeycomb.io"), 15 | service_name='frontend-python', 16 | debug=True, 17 | http_trace_parser_hook=w3c.http_trace_parser_hook, 18 | http_trace_propagation_hook=w3c.http_trace_propagation_hook 19 | ) 20 | 21 | 22 | app = HoneyWSGIMiddleware(create_app()) 23 | run_simple('0.0.0.0', 7777, app, use_debugger=True, use_reloader=True) 24 | -------------------------------------------------------------------------------- /python/frontend/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "frontend" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Honeycomb"] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.10" 9 | werkzeug = "^2.3.8" 10 | honeycomb-beeline = "^3.6.0" 11 | [tool.poetry.dev-dependencies] 12 | pytest = "^8.1.1" 13 | 14 | [build-system] 15 | requires = ["poetry>=0.12"] 16 | build-backend = "poetry.masonry.api" 17 | -------------------------------------------------------------------------------- /python/frontend/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honeycombio/example-greeting-service/6705c90566e5309e583e9c1312d87fe8a220cc1d/python/frontend/tests/__init__.py -------------------------------------------------------------------------------- /python/frontend/tests/test_frontend.py: -------------------------------------------------------------------------------- 1 | from frontend import __version__ 2 | 3 | 4 | def test_version(): 5 | assert __version__ == '0.1.0' 6 | -------------------------------------------------------------------------------- /python/message-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim 2 | 3 | WORKDIR /app 4 | 5 | ENV PYTHONFAULTHANDLER=1 \ 6 | PYTHONUNBUFFERED=1 \ 7 | PYTHONHASHSEED=random \ 8 | PIP_NO_CACHE_DIR=off \ 9 | PIP_DISABLE_PIP_VERSION_CHECK=on \ 10 | PIP_DEFAULT_TIMEOUT=100 \ 11 | POETRY_VERSION=1.3.2 12 | 13 | RUN pip install "poetry==$POETRY_VERSION" 14 | 15 | COPY poetry.lock pyproject.toml ./ 16 | RUN poetry config virtualenvs.create false \ 17 | && poetry install --no-root --no-interaction --no-ansi 18 | 19 | COPY ./message_service ./message_service 20 | 21 | EXPOSE 9000 22 | CMD ["poetry", "run", "python", "-m", "message_service"] 23 | -------------------------------------------------------------------------------- /python/message-service/README.rst: -------------------------------------------------------------------------------- 1 | message service implemented using `bottle`. -------------------------------------------------------------------------------- /python/message-service/message_service/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.0' 2 | -------------------------------------------------------------------------------- /python/message-service/message_service/__main__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | 4 | from bottle import Bottle, run 5 | from opentelemetry import trace 6 | from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import \ 7 | OTLPSpanExporter 8 | from opentelemetry.instrumentation.requests import RequestsInstrumentor 9 | from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware 10 | from opentelemetry.sdk.resources import Resource, SERVICE_NAME 11 | from opentelemetry.sdk.trace import TracerProvider 12 | from opentelemetry.sdk.trace.export import BatchSpanProcessor 13 | 14 | messages = [ 15 | "how are you?", "how are you doing?", "what's good?", "what's up?", "how do you do?", 16 | "sup?", "good day to you", "how are things?", "howzit?", "woohoo", 17 | ] 18 | trace.set_tracer_provider(TracerProvider( 19 | resource=Resource.create({SERVICE_NAME: "message-python"}) 20 | )) 21 | tracer = trace.get_tracer(os.getenv("OTEL_SERVICE_NAME", "message-tracer")) 22 | 23 | trace.get_tracer_provider().add_span_processor( 24 | BatchSpanProcessor(OTLPSpanExporter( 25 | headers=(("x-honeycomb-team", os.environ.get("HONEYCOMB_API_KEY")),), 26 | endpoint=os.environ.get("HONEYCOMB_API_ENDPOINT", 27 | "https://api.honeycomb.io") 28 | ))) 29 | 30 | app = Bottle() 31 | app.wsgi = OpenTelemetryMiddleware(app.wsgi) 32 | RequestsInstrumentor().instrument() 33 | 34 | @app.route('/message') 35 | @tracer.start_as_current_span("🤖 choosing message ✨") 36 | def message(): 37 | return random.choice(messages) 38 | 39 | run(app=app, host='0.0.0.0', port=9000) 40 | -------------------------------------------------------------------------------- /python/message-service/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "message-service" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Honeycomb"] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.10" 9 | bottle = "^0.12.25" 10 | opentelemetry-api = "^1.23.0" 11 | opentelemetry-sdk = "^1.23.0" 12 | opentelemetry-exporter-otlp-proto-grpc = "^1.23.0" 13 | opentelemetry-instrumentation-requests = "^0.44b0" 14 | opentelemetry-instrumentation-wsgi = "^0.44b0" 15 | requests = "^2.31.0" 16 | 17 | [tool.poetry.dev-dependencies] 18 | pytest = "^8.1.1" 19 | 20 | [build-system] 21 | requires = ["poetry>=0.12"] 22 | build-backend = "poetry.masonry.api" 23 | -------------------------------------------------------------------------------- /python/message-service/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honeycombio/example-greeting-service/6705c90566e5309e583e9c1312d87fe8a220cc1d/python/message-service/tests/__init__.py -------------------------------------------------------------------------------- /python/message-service/tests/test_message_service.py: -------------------------------------------------------------------------------- 1 | from message_service import __version__ 2 | 3 | 4 | def test_version(): 5 | assert __version__ == '0.1.0' 6 | -------------------------------------------------------------------------------- /python/name-service/.flaskenv: -------------------------------------------------------------------------------- 1 | FLASK_APP=name_service 2 | FLASK_RUN_PORT=8000 3 | -------------------------------------------------------------------------------- /python/name-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim 2 | 3 | WORKDIR /app 4 | 5 | ENV PYTHONFAULTHANDLER=1 \ 6 | PYTHONUNBUFFERED=1 \ 7 | PYTHONHASHSEED=random \ 8 | PIP_NO_CACHE_DIR=off \ 9 | PIP_DISABLE_PIP_VERSION_CHECK=on \ 10 | PIP_DEFAULT_TIMEOUT=100 \ 11 | POETRY_VERSION=1.3.2 12 | 13 | RUN pip install "poetry==$POETRY_VERSION" 14 | 15 | COPY poetry.lock pyproject.toml ./ 16 | RUN poetry config virtualenvs.create false \ 17 | && poetry install --no-root --no-interaction --no-ansi 18 | 19 | COPY ./.flaskenv ./ 20 | COPY ./name_service ./name_service 21 | 22 | EXPOSE 8000 23 | CMD ["poetry", "run", "opentelemetry-instrument", "flask", "run", "--host", "0.0.0.0"] 24 | -------------------------------------------------------------------------------- /python/name-service/README.rst: -------------------------------------------------------------------------------- 1 | name service implemented using `flask` and `requests` -------------------------------------------------------------------------------- /python/name-service/name_service/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.0' 2 | 3 | import logging 4 | import os 5 | import random 6 | 7 | import requests 8 | from flask import Flask 9 | from opentelemetry import trace 10 | 11 | names_by_year = { 12 | 2015: ['sophia', 'jackson', 'emma', 'aiden', 'olivia', 'liam', 'ava', 13 | 'lucas', 'mia', 'noah'], 14 | 2016: ['sophia', 'jackson', 'emma', 'aiden', 'olivia', 'lucas', 'ava', 15 | 'liam', 'mia', 'noah'], 16 | 2017: ['sophia', 'jackson', 'olivia', 'liam', 'emma', 'noah', 'ava', 17 | 'aiden', 'isabella', 'lucas'], 18 | 2018: ['sophia', 'jackson', 'olivia', 'liam', 'emma', 'noah', 'ava', 19 | 'aiden', 'isabella', 'caden'], 20 | 2019: ['sophia', 'liam', 'olivia', 'jackson', 'emma', 'noah', 'ava', 21 | 'aiden', 'aira', 'grayson'], 22 | 2020: ['olivia', 'noah', 'emma', 'liam', 'ava', 'elijah', 'isabella', 23 | 'oliver', 'sophia', 'lucas'] 24 | } 25 | 26 | YEAR_ENDPOINT = os.environ.get('YEAR_ENDPOINT', 27 | 'http://localhost:6001') + '/year' 28 | 29 | 30 | def get_year(): 31 | with tracer.start_as_current_span("✨ call /year ✨"): 32 | r = requests.get(YEAR_ENDPOINT) 33 | return int(r.text) 34 | 35 | tracer = trace.get_tracer(os.getenv("OTEL_SERVICE_NAME", "year-tracer")) 36 | 37 | logger = logging.getLogger("my-logger") 38 | logger.setLevel(logging.INFO) 39 | 40 | app = Flask(__name__) 41 | 42 | @app.route('/name') 43 | def get_name(): 44 | year = get_year() 45 | current_span = trace.get_current_span() 46 | logger.info("Selected year: %s", year) 47 | current_span.set_attribute("app.year_selected", year) 48 | 49 | with tracer.start_as_current_span("📖 look up name based on year ✨"): 50 | span = trace.get_current_span() 51 | names = names_by_year[year] 52 | name = random.choice(names) 53 | span.set_attribute("app.name_selected", name) 54 | return name 55 | -------------------------------------------------------------------------------- /python/name-service/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "name-service" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Honeycomb"] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.10" 9 | flask = "^2.3.2" 10 | python-dotenv = "^1.0.1" 11 | requests = "^2.31.0" 12 | opentelemetry-api = "^1.22.0" 13 | honeycomb-opentelemetry = "0.4.0b0" 14 | opentelemetry-instrumentation-requests = "^0.43b0" 15 | opentelemetry-instrumentation-flask = "^0.43b0" 16 | 17 | [tool.poetry.dev-dependencies] 18 | pytest = "^8.1.1" 19 | autopep8 = "^2.0.4" 20 | -------------------------------------------------------------------------------- /python/name-service/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honeycombio/example-greeting-service/6705c90566e5309e583e9c1312d87fe8a220cc1d/python/name-service/tests/__init__.py -------------------------------------------------------------------------------- /python/name-service/tests/test_name_service.py: -------------------------------------------------------------------------------- 1 | from name_service import __version__ 2 | 3 | 4 | def test_version(): 5 | assert __version__ == '0.1.0' 6 | -------------------------------------------------------------------------------- /python/year-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim 2 | 3 | WORKDIR /app 4 | 5 | ENV PYTHONFAULTHANDLER=1 \ 6 | PYTHONUNBUFFERED=1 \ 7 | PYTHONHASHSEED=random \ 8 | PIP_NO_CACHE_DIR=off \ 9 | PIP_DISABLE_PIP_VERSION_CHECK=on \ 10 | PIP_DEFAULT_TIMEOUT=100 \ 11 | POETRY_VERSION=1.3.2 12 | 13 | RUN pip install "poetry==$POETRY_VERSION" 14 | 15 | COPY poetry.lock pyproject.toml ./ 16 | RUN poetry config virtualenvs.create false \ 17 | && poetry install --no-root --no-interaction --no-ansi 18 | 19 | COPY ./yearservice ./yearservice 20 | 21 | EXPOSE 6001 22 | CMD ["poetry", "run", "yearservice/manage.py", "runserver", "0.0.0.0:6001", "--noreload"] 23 | -------------------------------------------------------------------------------- /python/year-service/README.rst: -------------------------------------------------------------------------------- 1 | year service implemented using `django`. -------------------------------------------------------------------------------- /python/year-service/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "year-service" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Honeycomb"] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.10" 9 | django = "^5.0.2" 10 | python-dotenv = "^1.0.1" 11 | opentelemetry-instrumentation-django = "^0.44b0" 12 | opentelemetry-sdk = "^1.23.0" 13 | opentelemetry-exporter-otlp-proto-grpc = "^1.23.0" 14 | 15 | [tool.poetry.dev-dependencies] 16 | pytest = "^8.1.1" 17 | autopep8 = "^2.0.4" 18 | -------------------------------------------------------------------------------- /python/year-service/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honeycombio/example-greeting-service/6705c90566e5309e583e9c1312d87fe8a220cc1d/python/year-service/tests/__init__.py -------------------------------------------------------------------------------- /python/year-service/tests/test_year_service.py: -------------------------------------------------------------------------------- 1 | from year_service import __version__ 2 | 3 | 4 | def test_version(): 5 | assert __version__ == '0.1.0' 6 | -------------------------------------------------------------------------------- /python/year-service/yearservice/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | from opentelemetry.instrumentation.django import DjangoInstrumentor 7 | 8 | 9 | def main(): 10 | """Run administrative tasks.""" 11 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'yearservice.settings') 12 | 13 | # This call is what makes the Django application be instrumented 14 | DjangoInstrumentor().instrument() 15 | try: 16 | from django.core.management import execute_from_command_line 17 | except ImportError as exc: 18 | raise ImportError( 19 | "Couldn't import Django. Are you sure it's installed and " 20 | "available on your PYTHONPATH environment variable? Did you " 21 | "forget to activate a virtual environment?" 22 | ) from exc 23 | execute_from_command_line(sys.argv) 24 | 25 | 26 | if __name__ == '__main__': 27 | main() 28 | -------------------------------------------------------------------------------- /python/year-service/yearservice/yearapp/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'yearapp.apps.YearappConfig' 2 | -------------------------------------------------------------------------------- /python/year-service/yearservice/yearapp/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /python/year-service/yearservice/yearapp/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honeycombio/example-greeting-service/6705c90566e5309e583e9c1312d87fe8a220cc1d/python/year-service/yearservice/yearapp/migrations/__init__.py -------------------------------------------------------------------------------- /python/year-service/yearservice/yearapp/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /python/year-service/yearservice/yearapp/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /python/year-service/yearservice/yearapp/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | import random 3 | import logging 4 | 5 | logger = logging.getLogger('my-logger') 6 | 7 | years = [2015, 2016, 2017, 2018, 2019, 2020] 8 | 9 | 10 | def year(request): 11 | year = random.choice(years) 12 | logger.info({'selected_year': year}) 13 | return HttpResponse(year) 14 | -------------------------------------------------------------------------------- /python/year-service/yearservice/yearservice/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honeycombio/example-greeting-service/6705c90566e5309e583e9c1312d87fe8a220cc1d/python/year-service/yearservice/yearservice/__init__.py -------------------------------------------------------------------------------- /python/year-service/yearservice/yearservice/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for yearservice project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'yearservice.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /python/year-service/yearservice/yearservice/urls.py: -------------------------------------------------------------------------------- 1 | """yearservice URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path 18 | from yearapp import views 19 | 20 | urlpatterns = [ 21 | path('admin/', admin.site.urls), 22 | path('year', views.year, name="year") 23 | ] 24 | -------------------------------------------------------------------------------- /python/year-service/yearservice/yearservice/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for yearservice project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'yearservice.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /ruby/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # When opening a file, EditorConfig plugins look for a file named .editorconfig 4 | # in the directory of the opened file and in every parent directory. 5 | # A search for .editorconfig files will stop if the root filepath is reached 6 | # or an EditorConfig file with root=true is found. 7 | 8 | # EditorConfig files are read top to bottom and the most recent rules found 9 | # take precedence. Properties from matching EditorConfig sections are applied 10 | # in the order they were read, so properties in closer files take precedence. 11 | 12 | # Ruby files 13 | [*.{rb,ru}] 14 | 15 | indent_size = 2 16 | 17 | # https://rubystyle.guide/ 18 | max_line_length = 80 19 | insert_final_newline = true 20 | trim_trailing_whitespace = true 21 | end_of_line = lf 22 | -------------------------------------------------------------------------------- /ruby/.envrc_example: -------------------------------------------------------------------------------- 1 | source_up 2 | layout ruby -------------------------------------------------------------------------------- /ruby/Tiltfile: -------------------------------------------------------------------------------- 1 | docker_compose("./docker-compose.yml") 2 | -------------------------------------------------------------------------------- /ruby/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.4" 2 | 3 | x-common-env: &common-env 4 | HONEYCOMB_API_KEY: 5 | HONEYCOMB_DATASET: 6 | HONEYCOMB_API: 7 | OTEL_EXPORTER_OTLP_ENDPOINT: 8 | OTEL_EXPORTER_OTLP_HEADERS: 9 | OTEL_RESOURCE_ATTRIBUTES: app.running-in=docker 10 | MESSAGE_ENDPOINT: http://message:9000 11 | NAME_ENDPOINT: http://name:8000 12 | YEAR_ENDPOINT: http://year:6001 13 | REDIS_URL: redis 14 | 15 | services: 16 | frontend: 17 | build: ./frontend 18 | image: hnyexample/frontend-ruby 19 | environment: 20 | <<: *common-env 21 | ports: 22 | - 7777:7777 23 | 24 | message: 25 | build: ./message-service 26 | image: hnyexample/message-ruby 27 | environment: 28 | <<: *common-env 29 | ports: 30 | - 9000:9000 31 | 32 | name: 33 | build: ./name-service 34 | image: hnyexample/name-ruby 35 | environment: 36 | <<: *common-env 37 | ports: 38 | - 8000:8000 39 | 40 | year: 41 | build: ./year-service 42 | image: hnyexample/year-ruby 43 | environment: 44 | <<: *common-env 45 | ports: 46 | - 6001:6001 47 | 48 | redis: 49 | image: redis:latest 50 | ports: 51 | - "127.0.0.1:6379:6379" -------------------------------------------------------------------------------- /ruby/frontend/.ruby-version: -------------------------------------------------------------------------------- 1 | 3.0.6 2 | -------------------------------------------------------------------------------- /ruby/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:3.0 2 | RUN gem install bundler 3 | WORKDIR /myapp 4 | COPY Gemfile* /myapp/ 5 | RUN bundle install 6 | COPY frontend.ru /myapp 7 | COPY o11y_wrapper.rb /myapp 8 | 9 | EXPOSE 7777 10 | 11 | HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD [ "curl", "--fail", "http://localhost:7777/up" ] 12 | CMD [ "bundle", "exec", "rackup", "frontend.ru", "--server", "puma", "--host", "0.0.0.0"] 13 | -------------------------------------------------------------------------------- /ruby/frontend/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rails' 4 | gem 'puma' 5 | gem 'opentelemetry-sdk' 6 | gem 'opentelemetry-exporter-otlp' 7 | gem 'opentelemetry-instrumentation-all' 8 | gem 'faraday' 9 | -------------------------------------------------------------------------------- /ruby/message-service/.ruby-version: -------------------------------------------------------------------------------- 1 | 3.0.6 2 | -------------------------------------------------------------------------------- /ruby/message-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:3.0 2 | RUN gem install bundler 3 | WORKDIR /myapp 4 | COPY Gemfile /myapp/Gemfile 5 | RUN bundle install 6 | COPY message.ru /myapp 7 | 8 | EXPOSE 9000 9 | CMD [ "bundle", "exec", "rackup", "message.ru", "--server", "puma", "--host", "0.0.0.0"] 10 | -------------------------------------------------------------------------------- /ruby/message-service/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rails' 4 | gem 'puma' 5 | gem 'honeycomb-beeline' 6 | gem 'faraday' 7 | gem 'rackup' 8 | -------------------------------------------------------------------------------- /ruby/name-service/.ruby-version: -------------------------------------------------------------------------------- 1 | 3.0.6 2 | -------------------------------------------------------------------------------- /ruby/name-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:3.0 2 | RUN gem install bundler 3 | WORKDIR /myapp 4 | COPY Gemfile /myapp/Gemfile 5 | RUN bundle install 6 | COPY name.rb /myapp 7 | 8 | EXPOSE 8000 9 | CMD [ "bundle", "exec", "ruby", "name.rb"] 10 | -------------------------------------------------------------------------------- /ruby/name-service/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "honeycomb-beeline" 4 | gem "sinatra" 5 | gem "faraday" 6 | gem "rackup" 7 | -------------------------------------------------------------------------------- /ruby/name-service/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.8.6) 5 | public_suffix (>= 2.0.2, < 6.0) 6 | base64 (0.2.0) 7 | domain_name (0.6.20240107) 8 | excon (0.110.0) 9 | faraday (2.9.0) 10 | faraday-net_http (>= 2.0, < 3.2) 11 | faraday-net_http (3.1.0) 12 | net-http 13 | ffi (1.16.3) 14 | ffi-compiler (1.3.2) 15 | ffi (>= 1.15.5) 16 | rake 17 | honeycomb-beeline (3.1.0) 18 | libhoney (>= 2.3.0) 19 | http (5.2.0) 20 | addressable (~> 2.8) 21 | base64 (~> 0.1) 22 | http-cookie (~> 1.0) 23 | http-form_data (~> 2.2) 24 | llhttp-ffi (~> 0.5.0) 25 | http-cookie (1.0.5) 26 | domain_name (~> 0.5) 27 | http-form_data (2.3.0) 28 | libhoney (2.3.0) 29 | addressable (~> 2.0) 30 | excon 31 | http (>= 2.0, < 6.0) 32 | llhttp-ffi (0.5.0) 33 | ffi-compiler (~> 1.0) 34 | rake (~> 13.0) 35 | mustermann (3.0.0) 36 | ruby2_keywords (~> 0.0.1) 37 | net-http (0.4.1) 38 | uri 39 | public_suffix (5.0.4) 40 | rack (3.0.9.1) 41 | rack-protection (4.0.0) 42 | base64 (>= 0.1.0) 43 | rack (>= 3.0.0, < 4) 44 | rack-session (2.0.0) 45 | rack (>= 3.0.0) 46 | rackup (2.1.0) 47 | rack (>= 3) 48 | webrick (~> 1.8) 49 | rake (13.1.0) 50 | ruby2_keywords (0.0.5) 51 | sinatra (4.0.0) 52 | mustermann (~> 3.0) 53 | rack (>= 3.0.0, < 4) 54 | rack-protection (= 4.0.0) 55 | rack-session (>= 2.0.0, < 3) 56 | tilt (~> 2.0) 57 | tilt (2.3.0) 58 | uri (0.13.0) 59 | webrick (1.8.1) 60 | 61 | PLATFORMS 62 | arm64-darwin-23 63 | 64 | DEPENDENCIES 65 | faraday 66 | honeycomb-beeline 67 | rackup 68 | sinatra 69 | 70 | BUNDLED WITH 71 | 2.2.33 72 | -------------------------------------------------------------------------------- /ruby/name-service/name.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra' 2 | require 'honeycomb-beeline' 3 | require 'honeycomb/propagation/w3c' 4 | require 'faraday' 5 | 6 | Honeycomb.configure do |config| 7 | config.write_key = ENV['HONEYCOMB_API_KEY'] 8 | config.service_name = ENV['SERVICE_NAME'] || "name-ruby" 9 | config.api_host = ENV['HONEYCOMB_API_ENDPOINT'] 10 | config.http_trace_parser_hook do |env| 11 | Honeycomb::W3CPropagation::UnmarshalTraceContext.parse_rack_env(env) 12 | end 13 | config.http_trace_propagation_hook do |env, context| 14 | Honeycomb::W3CPropagation::MarshalTraceContext.parse_faraday_env(env, context) 15 | end 16 | #config.client = Libhoney::LogClient.new 17 | end 18 | 19 | use Honeycomb::Sinatra::Middleware, client: Honeycomb.client 20 | 21 | set :bind, '0.0.0.0' 22 | set :port, 8000 23 | 24 | names_by_year = { 25 | 2015 => ['sophia', 'jackson', 'emma', 'aiden', 'olivia', 'liam', 'ava', 'lucas', 'mia', 'noah'], 26 | 2016 => ['sophia', 'jackson', 'emma', 'aiden', 'olivia', 'lucas', 'ava', 'liam', 'mia', 'noah'], 27 | 2017 => ['sophia', 'jackson', 'olivia', 'liam', 'emma', 'noah', 'ava', 'aiden', 'isabella', 'lucas'], 28 | 2018 => ['sophia', 'jackson', 'olivia', 'liam', 'emma', 'noah', 'ava', 'aiden', 'isabella', 'caden'], 29 | 2019 => ['sophia', 'liam', 'olivia', 'jackson', 'emma', 'noah', 'ava', 'aiden', 'aira', 'grayson'], 30 | 2020 => ['olivia', 'noah', 'emma', 'liam', 'ava', 'elijah', 'isabella', 'oliver', 'sophia', 'lucas'] 31 | } 32 | 33 | get '/name' do 34 | year = get_year 35 | names = names_by_year[year] 36 | names[rand(names.length)] 37 | end 38 | 39 | def get_year 40 | year_service_connection = Faraday.new(ENV["YEAR_ENDPOINT"] || "http://localhost:6001") 41 | year_service_response = year_service_connection.get("/year") do |request| 42 | end 43 | year_service_response.body.to_i 44 | end 45 | -------------------------------------------------------------------------------- /ruby/year-service/.ruby-version: -------------------------------------------------------------------------------- 1 | 3.0.6 2 | -------------------------------------------------------------------------------- /ruby/year-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:3.0 2 | RUN gem install bundler 3 | WORKDIR /app 4 | COPY Gemfile /app/Gemfile 5 | COPY Gemfile.lock /app/Gemfile.lock 6 | RUN bundle install 7 | COPY config.ru /app 8 | COPY o11y_wrapper.rb /app 9 | 10 | EXPOSE 6001 11 | CMD ["bundle", "exec", "puma", "--port", "6001"] 12 | -------------------------------------------------------------------------------- /ruby/year-service/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem 'opentelemetry-sdk' 4 | gem 'opentelemetry-exporter-otlp' 5 | gem 'opentelemetry-instrumentation-all' 6 | gem "grape" 7 | gem "puma" 8 | -------------------------------------------------------------------------------- /ruby/year-service/config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/setup" 4 | Bundler.require 5 | 6 | require 'opentelemetry/sdk' 7 | require 'opentelemetry/exporter/otlp' 8 | require 'opentelemetry/instrumentation/all' 9 | require_relative './o11y_wrapper.rb' 10 | 11 | begin 12 | OpenTelemetry::SDK.configure do |c| 13 | c.service_name = ENV['SERVICE_NAME'] || "year-ruby" 14 | 15 | # enable all auto-instrumentation available 16 | c.use_all() 17 | 18 | # add the Baggage and CarryOn processors to thepipeline 19 | c.add_span_processor(O11yWrapper::BaggageSpanProcessor.new) 20 | c.add_span_processor(O11yWrapper::CarryOnSpanProcessor.new) 21 | 22 | # Because we tinkered with the pipeline, we'll need to 23 | # wire up span batching and sending via OTLP ourselves. 24 | # This is usually the default. 25 | c.add_span_processor( 26 | OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new( 27 | OpenTelemetry::Exporter::OTLP::Exporter.new() 28 | ) 29 | ) 30 | end 31 | rescue OpenTelemetry::SDK::ConfigurationError => e 32 | puts "What now?" 33 | puts e.inspect 34 | end 35 | 36 | Tracer = OpenTelemetry.tracer_provider.tracer("year-internal") 37 | 38 | class App < Grape::API 39 | format :txt 40 | 41 | get :year do 42 | Tracer.in_span("🗓 get-a-year ✨") do 43 | sleep rand(0..0.005) 44 | (2015..2020).to_a.sample 45 | end 46 | end 47 | end 48 | 49 | use OpenTelemetry::Instrumentation::Rack::Middlewares::TracerMiddleware 50 | run App 51 | -------------------------------------------------------------------------------- /web/.eslintignore: -------------------------------------------------------------------------------- 1 | eslintrc.js -------------------------------------------------------------------------------- /web/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | }, 6 | extends: 'eslint:recommended', 7 | parserOptions: { 8 | ecmaVersion: 'latest', 9 | sourceType: 'module', 10 | }, 11 | rules: {}, 12 | }; 13 | -------------------------------------------------------------------------------- /web/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bld -------------------------------------------------------------------------------- /web/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'es5', 3 | tabWidth: 2, 4 | singleQuote: true, 5 | printWidth: 100, 6 | }; 7 | -------------------------------------------------------------------------------- /web/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 8 | # where available (npm@5+) 9 | COPY package*.json ./ 10 | 11 | RUN npm install 12 | # If you are building your code for production 13 | # RUN npm ci --only=production 14 | 15 | # Bundle app source 16 | COPY . . 17 | RUN npm run build 18 | 19 | EXPOSE 8080 20 | CMD [ "npm", "start" ] 21 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # Browser greeting service 2 | 3 | A browser app that sends OTel traces to a collector that passes it through to Honeycomb. The browser app makes a request to `http://localhost:7777/greeting` to trace HTTP requests from the frontend through to the backend. 4 | 5 | ## Running the app 6 | 7 | This will run the browser app that generates a greeting through JS code in the browser. To run the browser app so that it gets a greeting from a server run. 8 | 9 | ```shell 10 | tilt up web node 11 | ``` 12 | 13 | It should be okay to run with other language backend services too, but they may not be configured with CORS to allow requests from localhost:8080, the node app definitely is so it's safest to use that one. 14 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "1.0.0", 4 | "description": "An example browser app that sends traces through Opentelemetry", 5 | "scripts": { 6 | "build": "webpack", 7 | "lint": "eslint src", 8 | "lint:fix": "eslint src --fix", 9 | "start": "webpack serve --open", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@honeycombio/opentelemetry-web": "^0.0.1", 16 | "@opentelemetry/api": "1.7.0", 17 | "@opentelemetry/auto-instrumentations-web": "^0.36.0" 18 | }, 19 | "devDependencies": { 20 | "assert": "^2.0.0", 21 | "browserify-zlib": "^0.2.0", 22 | "buffer": "^6.0.3", 23 | "eslint": "^8.37.0", 24 | "events": "^3.3.0", 25 | "html-webpack-plugin": "^5.5.0", 26 | "os-browserify": "^0.3.0", 27 | "path-browserify": "^1.0.1", 28 | "prettier": "2.7.1", 29 | "prettier-eslint": "^15.0.1", 30 | "process": "^0.11.10", 31 | "querystring-es3": "^0.2.1", 32 | "stream-browserify": "^3.0.0", 33 | "stream-http": "^3.2.0", 34 | "url": "^0.11.0", 35 | "util": "^0.12.5", 36 | "webpack": "^5.76.0", 37 | "webpack-cli": "^5.0.2", 38 | "webpack-dev-server": "^4.9.2" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /web/src/tracing-http.js: -------------------------------------------------------------------------------- 1 | import { HoneycombWebSDK } from '@honeycombio/opentelemetry-web'; 2 | import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web'; 3 | const sdk = new HoneycombWebSDK({ 4 | // we're sending data to a local collector here to avoid exposing the API key in the browser 5 | endpoint: 'http://localhost:55681/v1/traces', 6 | serviceName: 'egs-browser', 7 | instrumentations: [getWebAutoInstrumentations()], 8 | }); 9 | sdk.start(); 10 | -------------------------------------------------------------------------------- /web/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const webpack = require("webpack"); 4 | 5 | module.exports = { 6 | entry: { 7 | index: './src/index.js', 8 | 'tracing-http': './src/tracing-http.js', 9 | }, 10 | devtool: 'inline-source-map', 11 | devServer: { 12 | static: './bld', 13 | }, 14 | mode: 'development', 15 | output: { 16 | filename: '[name].bundle.js', 17 | path: path.resolve(__dirname, 'dist'), 18 | clean: true, 19 | }, 20 | optimization: { 21 | runtimeChunk: 'single', 22 | }, 23 | plugins: [ 24 | new HtmlWebpackPlugin({ 25 | title: 'Development', 26 | }), 27 | new webpack.DefinePlugin({ 28 | FRONTEND_ENDPOINT: JSON.stringify(process.env.FRONTEND_ENDPOINT || 'http://localhost:7777'), 29 | }), 30 | ], 31 | resolve: { 32 | fallback: { 33 | path: require.resolve('path-browserify'), 34 | }, 35 | }, 36 | }; 37 | --------------------------------------------------------------------------------