Compare commits

..

No commits in common. "main" and "0.0.3" have entirely different histories.
main ... 0.0.3

1432 changed files with 18054 additions and 93181 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@ -12,10 +12,5 @@ trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.yaml]
indent_size = 2
[*.yml]
indent_size = 2
[Makefile]
indent_style = tab

View File

@ -1,5 +0,0 @@
PHP_USER=php
XDEBUG_MODE=debug
PHP_CS_FIXER_IGNORE_ENV=true
#docker-compose or "docker compose" for v1/v2 respectively, note that v1 support ends 06/2023
DOCKER_COMPOSE=docker compose

View File

@ -17,5 +17,3 @@ What did you see instead?
**Additional context**
Add any other context about the problem here.
<sub>**Tip**: [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/).</sub>

View File

@ -17,5 +17,3 @@ Which alternative solutions or features have you considered?
**Additional context**
Add any other context about the feature request here.
<sub>**Tip**: [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/).</sub>

5
.github/config.yml vendored
View File

@ -1,5 +0,0 @@
# configuration for new-issue-welcome bot https://github.com/behaviorbot/new-issue-welcome
newPRWelcomeComment: >
Thanks for opening your first pull request! If you haven't yet signed our Contributor License Agreement (CLA),
then please do so that we can accept your contribution.
A link should appear shortly in this PR if you have not already signed one.

View File

@ -1,22 +0,0 @@
---
version: 2
updates:
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "docker"
directory: "/docker"
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "docker-compose"
directory: "/"
schedule:
interval: "daily"

21
.github/stale.yml vendored
View File

@ -1,21 +0,0 @@
---
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 30
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- 'pinned'
- 'security'
- 'help wanted'
# Label to use when marking an issue as stale
staleLabel: 'stale'
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
closeComment: >
This issue has been automatically closed because it has not had
recent activity, but it can be reopened.
Thank you for your contributions.

35
.github/workflows/docs-update.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: Update OpenTelemetry Website Docs
on:
# triggers only on a manual dispatch
workflow_dispatch:
jobs:
update-docs:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: make-pr
env:
API_TOKEN_GITHUB: ${{secrets.GITHUB_PAT_TOKEN}}
# Destination repo should always be 'open-telemetry/opentelemetry.io'
DESTINATION_REPO: open-telemetry/opentelemetry.io
# Destination path should be the absolute path to your language's friendly name in the docs tree (i.e, 'content/en/docs/java')
DESTINATION_PATH: content/en/docs/php
# Source path should be 'website_docs', all files and folders are copied from here to dest
SOURCE_PATH: website_docs
run: |
TARGET_DIR=$(mktemp -d)
export GITHUB_TOKEN=$API_TOKEN_GITHUB
git config user.name github-actions
git config user.email github-actions@github.com
git clone "https://$API_TOKEN_GITHUB@github.com/$DESTINATION_REPO.git" "$TARGET_DIR"
cp -r $SOURCE_PATH/* "$TARGET_DIR/$DESTINATION_PATH"
cd "$TARGET_DIR"
git checkout -b docs-$GITHUB_REPOSITORY-$GITHUB_SHA
git add .
git commit -m "Docs update from $GITHUB_REPOSITORY"
git push -u origin HEAD:docs-$GITHUB_REPOSITORY-$GITHUB_SHA
gh pr create -t "Docs Update from $GITHUB_REPOSITORY" -b "This is an automated pull request." -B main -H docs-$GITHUB_REPOSITORY-$GITHUB_SHA
echo "done"

View File

@ -1,20 +0,0 @@
name: FOSSA scanning
on:
push:
branches:
- main
permissions:
contents: read
jobs:
fossa:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: fossas/fossa-action@3ebcea1862c6ffbd5cf1b4d0bd6b3fe7bd6f2cac # v1.7.0
with:
api-key: ${{secrets.FOSSA_API_KEY}}
team: OpenTelemetry

View File

@ -1,50 +0,0 @@
name: OSSF Scorecard
on:
push:
branches:
- main
schedule:
- cron: "17 0 * * 1" # once a week
workflow_dispatch:
permissions: read-all
jobs:
analysis:
runs-on: ubuntu-latest
permissions:
# Needed for Code scanning upload
security-events: write
# Needed for GitHub OIDC token if publish_results is true
id-token: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
with:
results_file: results.sarif
results_format: sarif
publish_results: true
# file_mode is needed in this repo because .gitattributes excludes the .github directory
# (see https://github.com/ossf/scorecard/issues/4679#issuecomment-3013550752)
file_mode: git
# Upload the results as artifacts (optional). Commenting out will disable
# uploads of run results in SARIF format to the repository Actions tab.
# https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts
- name: "Upload artifact"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5
with:
sarif_file: results.sarif

View File

@ -1,4 +1,5 @@
name: PHP QA
name: PHP Composer
on:
push:
@ -6,170 +7,61 @@ on:
pull_request:
branches: [ main ]
permissions:
contents: read
jobs:
php:
build:
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
php-version: ['8.1', '8.2', '8.3']
experimental: [false]
composer_args: [""]
include:
- php-version: 8.4
experimental: false
composer_args: "--ignore-platform-reqs"
- php-version: 8.5
experimental: true
composer_args: "--ignore-platform-reqs"
env:
extensions: ast, grpc, opentelemetry, protobuf
operating-system: [ubuntu-latest]
php-versions: ['7.3', '7.4', '8.0']
steps:
- name: Set cache key
id: key
run: |
echo "key=$(date +'%Y-%U')" >> $GITHUB_ENV
- uses: actions/checkout@v4
- uses: gacts/run-and-post-run@v1
id: post-run-command
with:
post: |
echo "::group::Steps"
echo "composer=${{steps.composer.outcome}}"
echo "style=${{steps.style.outcome}}"
echo "deps=${{steps.deps.outcome}}"
echo "phan=${{steps.phan.outcome}}"
echo "psalm=${{steps.psalm.outcome}}"
echo "phpstan=${{steps.phpstan.outcome}}"
echo "unit=${{steps.unit.outcome}}"
echo "integration=${{steps.integration.outcome}}"
echo "::endgroup::"
if [ ${{ steps.composer.outcome == 'failure' || steps.style.outcome == 'failure' || steps.deps.outcome == 'failure' || steps.phan.outcome == 'failure' || steps.psalm.outcome == 'failure' || steps.phpstan.outcome == 'failure' || steps.unit.outcome == 'failure' || steps.integration.outcome == 'failure' }} == true ]; then \
echo "::error::One or more steps failed"; \
fi
- name: Setup cache environment
id: extcache
uses: shivammathur/cache-extensions@v1
with:
php-version: ${{ matrix.php-version }}
extensions: ${{ env.extensions }}
key: ${{ env.key }}
- name: Cache extensions
uses: actions/cache@v4
with:
path: ${{ steps.extcache.outputs.dir }}
key: ${{ steps.extcache.outputs.key }}
restore-keys: ${{ steps.extcache.outputs.key }}
- uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
php-version: ${{ matrix.php-versions }}
coverage: xdebug
tools: php-cs-fixer
extensions: ${{ env.extensions }}
extensions: ast, grpc
- name: Validate composer.json
- name: Validate composer.json and composer.lock
run: composer validate
- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v4
uses: actions/cache@v2
with:
path: vendor
key: ${{ runner.os }}-${{ matrix.php-version }}-vendor-${{ hashFiles('composer.json') }}
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.json') }}
restore-keys: |
${{ runner.os }}-${{ matrix.php-version }}-vendor-
- name: Cache test tools
id: test-tools-cache
uses: actions/cache@v4
with:
path: vendor-bin
key: ${{ runner.os }}-${{ matrix.php-version }}-vendor-bin-${{ hashFiles('vendor-bin/*/composer.json') }}
restore-keys: |
${{ runner.os }}-${{ matrix.php-version }}-vendor-bin-
${{ runner.os }}-php-
- name: Install dependencies
id: composer
if: steps.composer-cache.outputs.cache-hit != 'true'
run: |
composer --version
composer install --prefer-dist --no-progress ${{ matrix.composer_args }}
run: composer install --prefer-dist --no-progress --no-suggest
- name: Update Composer
run: composer update
- name: Check Style
id: style
continue-on-error: ${{ matrix.experimental }}
env:
PHP_CS_FIXER_IGNORE_ENV: 1
run: |
vendor-bin/php-cs-fixer/vendor/bin/php-cs-fixer --version
vendor-bin/php-cs-fixer/vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --dry-run --stop-on-violation --using-cache=no -vvv
- name: Check Dependencies
id: deps
continue-on-error: ${{ matrix.experimental }}
run: |
vendor-bin/deptrac/vendor/bin/deptrac --version
vendor-bin/deptrac/vendor/bin/deptrac --formatter=github-actions --report-uncovered
run: vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --dry-run --stop-on-violation --using-cache=no -vvv
- name: Run Phan
id: phan
continue-on-error: ${{ matrix.experimental }}
env:
XDEBUG_MODE: off
PHAN_DISABLE_XDEBUG_WARN: 1
run: |
vendor-bin/phan/vendor/bin/phan --version
vendor-bin/phan/vendor/bin/phan
run: vendor/bin/phan
- name: Run Psalm
id: psalm
continue-on-error: ${{ matrix.experimental }}
run: |
vendor-bin/psalm/vendor/bin/psalm --version
vendor-bin/psalm/vendor/bin/psalm --output-format=github
run: vendor/bin/psalm --output-format=github
- name: Run Phpstan
id: phpstan
continue-on-error: ${{ matrix.experimental }}
run: |
vendor/bin/phpstan --version
vendor/bin/phpstan analyse --error-format=github
run: vendor/bin/phpstan analyse --error-format=github
- name: Run PHPUnit (unit tests)
id: unit
continue-on-error: ${{ matrix.experimental }}
run: |
vendor/bin/phpunit --version
php -dzend.assertions=1 vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover --testsuite unit
- name: Run PHPUnit (integration tests)
id: integration
continue-on-error: ${{ matrix.experimental }}
run: vendor/bin/phpunit --testsuite integration
- name: Run PHPUnit
run: vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover
- name: Code Coverage
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.clover
flags: ${{ matrix.php-version }}
verbose: false
packages:
uses: opentelemetry-php/gh-workflows/.github/workflows/validate-packages.yml@main
needs: php
with:
matrix_extension: '["ast, json, grpc"]'
matrix_php_version: '["8.1", "8.2", "8.3"]'
install_directory: '~/.test/.packages'
run: bash <(curl -s https://codecov.io/bash)

View File

@ -1,34 +0,0 @@
name: "Generate API Documentation"
on:
push:
branches:
- "main"
workflow_dispatch:
permissions:
contents: read
jobs:
documentation:
permissions:
pages: write # required for GitHub Pages deployment
id-token: write # required for GitHub Pages deployment
name: "Documentation"
runs-on: "ubuntu-latest"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
- name: "Build"
uses: "phpDocumentor/phpDocumentor@v3.8.1"
with:
target: "docs/build"
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: 'docs/build'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

View File

@ -0,0 +1,24 @@
name: Psalm Security Analysis
on: [push, pull_request]
jobs:
psalm:
name: Psalm
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Psalm
uses: docker://vimeo/psalm-github-actions
with:
composer_require_dev: true
composer_ignore_platform_reqs: true
security_analysis: true
report_file: results.sarif
- name: Upload Security Analysis results to GitHub
uses: github/codeql-action/upload-sarif@v1
with:
sarif_file: results.sarif

View File

@ -1,59 +0,0 @@
name: publish-otel-php-base-docker-image
on:
schedule:
- cron: "0 0 * * 0"
workflow_dispatch:
push:
paths:
- docker/Dockerfile
- .github/workflows/publish-otel-php-base-docker-image.yml
pull_request:
paths:
- docker/Dockerfile
- .github/workflows/publish-otel-php-base-docker-image.yml
permissions:
contents: read
jobs:
push_to_registry:
name: OpenTelemetry PHP base docker image creation
strategy:
matrix:
php-version: ['8.1', '8.2', '8.3', '8.4']
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
timeout-minutes: 500
steps:
- name: check out the repo
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push ${{ matrix.php-version }} to ghcr.io
uses: docker/build-push-action@v6
if: github.ref != 'refs/heads/main'
with:
push: false
file: docker/Dockerfile
build-args: PHP_VERSION=${{ matrix.php-version }}
platforms: linux/amd64,linux/arm/v8,linux/arm64
- name: Build and push ${{ matrix.php-version }} to ghcr.io
uses: docker/build-push-action@v6
if: github.ref == 'refs/heads/main'
with:
push: true
file: docker/Dockerfile
build-args: PHP_VERSION=${{ matrix.php-version }}
platforms: linux/amd64,linux/arm/v8,linux/arm64
tags: ghcr.io/open-telemetry/opentelemetry-php/opentelemetry-php-base:${{ matrix.php-version }}

View File

@ -1,22 +0,0 @@
name: Shellcheck
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
permissions:
contents: read
jobs:
shellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install shellcheck
run: sudo apt update && sudo apt install --assume-yes shellcheck
- name: Run shellcheck
run: find . -name \*.sh | xargs shellcheck --severity=warning

View File

@ -1,26 +0,0 @@
name: gitsplit
on:
push:
branches:
- main
- split
release:
types: [published]
create:
workflow_dispatch:
permissions:
contents: read
jobs:
gitsplit:
runs-on: ubuntu-latest
steps:
- name: checkout
run: git clone "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" "$GITHUB_WORKSPACE" && cd "$GITHUB_WORKSPACE" && git checkout $GITHUB_SHA
- name: Split repositories
uses: docker://jderusse/gitsplit:latest
with:
args: gitsplit
env:
GH_TOKEN: ${{ secrets.GITSPLIT_TOKEN }}

34
.gitignore vendored
View File

@ -1,38 +1,12 @@
composer.phar
composer.lock
var/
vendor/
vendor-bin/**/vendor/
vendor-bin/**/composer.lock
var
vendor
.idea/
coverage.clover
tests/coverage
.php-cs-fixer.cache
.phpbench
!tests/coverage/html/.gitkeep
var/metrics/
!var/metrics/.gitkeep
junit.xml
.env
# IntelliJ IDEA
.idea
*.iml
# VS Code
.vscode
# OS X
.DS_Store
.php_cs.cache
# W3C Test Service build artifacts
tests/TraceContext/W3CTestService/test_app
tests/TraceContext/W3CTestService/trace-context
# deptrac cache
/.deptrac.cache
# output from phpdoc
docs/build
# cache from phpdoc
.phpdoc

View File

@ -1,37 +0,0 @@
# Path to a cache directory Used to speed up the split over time by reusing git's objects
cache_url: "/cache/gitsplit"
# Path to the repository to split (default = current path)
project_url: "https://github.com/open-telemetry/opentelemetry-php.git"
# List of splits.
splits:
- prefix: "proto/otel"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/gen-otlp-protobuf.git"
- prefix: "src/Context"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/context.git"
- prefix: "src/SemConv"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/sem-conv.git"
- prefix: "src/API"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/api.git"
- prefix: "src/SDK"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/sdk.git"
- prefix: "src/Config/SDK"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/config-sdk.git"
- prefix: "src/Contrib/Otlp"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/exporter-otlp.git"
- prefix: "src/Contrib/Grpc"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/transport-grpc.git"
- prefix: "src/Contrib/Zipkin"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/exporter-zipkin.git"
- prefix: "src/Extension/Propagator/B3"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/extension-propagator-b3.git"
- prefix: "src/Extension/Propagator/CloudTrace"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/extension-propagator-cloudtrace.git"
- prefix: "src/Extension/Propagator/Jaeger"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/extension-propagator-jaeger.git"
# List of references to split (defined as regexp)
origins:
- ^main$
- ^split$

View File

@ -1,7 +1,5 @@
<?php
declare(strict_types=1);
use Phan\Issue;
/**
@ -44,7 +42,7 @@ return [
//
// Note that the **only** effect of choosing `'5.6'` is to infer that functions removed in php 7.0 exist.
// (See `backward_compatibility_checks` for additional options)
'target_php_version' => '8.1',
'target_php_version' => '7.3',
// If enabled, missing properties will be created when
// they are first seen. If false, we'll report an
@ -280,14 +278,7 @@ return [
// Add any issue types (such as `'PhanUndeclaredMethod'`)
// to this deny-list to inhibit them from being reported.
'suppress_issue_types' => [
'PhanAccessClassInternal',
'PhanAccessMethodInternal',
'PhanAccessPropertyInternal',
'PhanTypeMismatchPropertyReal',
'PhanTemplateTypeNotUsedInFunctionReturn',
'PhanUndeclaredClassAttribute',
],
'suppress_issue_types' => [],
// A regular expression to match files to be excluded
// from parsing and analysis and will not be read at all.
@ -303,9 +294,7 @@ return [
//
// This is useful for excluding hopelessly unanalyzable
// files that can't be removed for whatever reason.
'exclude_file_list' => [
'vendor/composer/composer/src/Composer/InstalledVersions.php',
],
'exclude_file_list' => [],
// A directory list that defines files that will be excluded
// from static analysis, but whose class and method
@ -321,7 +310,6 @@ return [
'exclude_analysis_directory_list' => [
'vendor/',
'proto/',
'src/Config/SDK',
],
// Enable this to enable checks of require/include statements referring to valid paths.
@ -369,20 +357,19 @@ return [
// Thus, both first-party and third-party code being used by
// your application should be included in this list.
'directory_list' => [
'src',
'proto/otel/GPBMetadata',
'proto/otel/Opentelemetry',
'vendor/composer',
'vendor/grpc/grpc/src/lib',
'api',
'Context',
'sdk',
'contrib',
'proto',
'vendor/composer/xdebug-handler/src',
'vendor/guzzlehttp',
'vendor/tbachert/spi/src',
'vendor/psr',
'vendor/php-http',
'vendor/phan/phan/src/Phan',
'vendor/phpunit/phpunit/src',
'vendor/google/protobuf/src',
'vendor/ramsey/uuid/src',
'vendor/nyholm/psr7-server/src',
'vendor/symfony/config',
'vendor/promphp/prometheus_client_php/src',
'vendor/google/protobuf/src'
],
// A list of individual files to include in analysis

View File

@ -1,14 +1,11 @@
<?php
declare(strict_types=1);
$finder = PhpCsFixer\Finder::create()
->in('examples/')
->in('tests/')
->in('src/');
->exclude('vendor')
->exclude('var/cache')
->exclude('proto')
->in(__DIR__);
$config = new PhpCsFixer\Config();
return $config->setRules([
'concat_space' => ['spacing' => 'one'],
'declare_equal_normalize' => ['space' => 'none'],
@ -16,7 +13,6 @@ return $config->setRules([
'modernize_types_casting' => true,
'ordered_imports' => true,
'php_unit_construct' => true,
'php_unit_method_casing' => ['case' => 'snake_case'],
'single_line_comment_style' => true,
'yoda_style' => false,
'@PSR2' => true,
@ -25,15 +21,12 @@ return $config->setRules([
'blank_line_before_statement' => true,
'cast_spaces' => true,
'declare_strict_types' => true,
'type_declaration_spaces' => true,
'function_typehint_space' => true,
'include' => true,
'lowercase_cast' => true,
'new_with_parentheses' => true,
'no_blank_lines_after_class_opening' => true,
'new_with_braces' => true,
'no_extra_blank_lines' => true,
'no_leading_import_slash' => true,
'no_trailing_whitespace' => true,
'no_whitespace_in_blank_line' => true,
'echo_tag_syntax' => true,
'no_unused_imports' => true,
'no_useless_else' => true,
@ -42,9 +35,9 @@ return $config->setRules([
'phpdoc_scalar' => true,
'phpdoc_types' => true,
'short_scalar_cast' => true,
'blank_lines_before_namespace' => true,
'single_blank_line_before_namespace' => true,
'single_quote' => true,
'trailing_comma_in_multiline' => ['elements' => ['arrays', 'parameters', 'match']],
'trailing_comma_in_multiline' => true,
])
->setRiskyAllowed(true)
->setFinder($finder);

View File

@ -12,4 +12,4 @@
# https://help.github.com/en/articles/about-code-owners
#
* @open-telemetry/php-approvers
* @bobstrecansky @beniamin @zsistla

View File

@ -1,319 +1,19 @@
# Contributing Guide
Maintainers ([@open-telemetry/php-maintainers](https://github.com/orgs/open-telemetry/teams/php-maintainers)):
## Introduction
- [Bob Strecansky](https://github.com/bobstrecansky), Mailchimp
Welcome to the OpenTelemetry PHP repository! We appreciate your interest in contributing and helping improve this project. No contribution is too small—whether you're fixing a typo, improving documentation, or implementing a major feature, we value your help.
Find more about the maintainer role in [community repository](https://github.com/open-telemetry/community/blob/master/community-membership.md#maintainer)
This repository is part of the larger OpenTelemetry ecosystem, aimed at providing observability solutions for PHP applications. If you have any questions, feel free to ask in our community channels (See further help below). Your contributions make a difference!
Approvers ([@open-telemetry/php-approvers](https://github.com/orgs/open-telemetry/teams/php-approvers)):
## Pre-requisites
- [Levi Morrison](https://github.com/morrisonlevi), Datadog
- [Austin Schoen](https://github.com/AustinSchoen), Mailchimp
- [Beniamin Calota](https://github.com/beniamin), eMag
To contribute effectively, ensure you have the following tools installed:
Find more information about the approver role in the [community repository](https://github.com/open-telemetry/community/blob/master/community-membership.md#approver)
* PHP 8.1 or higher (Check supported PHP versions)
Triagers ([@open-telemetry/php-triagers](https://github.com/orgs/open-telemetry/teams/php-triagers)):
We aim to support officially supported PHP versions, according to https://www.php.net/supported-versions.php. The
developer image `ghcr.io/open-telemetry/opentelemetry-php/opentelemetry-php-base` is tagged as `8.1`, `8.2` and `8.3`
respectively, with `8.1` being the default. You can execute the test suite against other PHP versions by running the
following command:
- [Jodee Varney](https://github.com/jodeev), Splunk
```bash
PHP_VERSION=8.1 make all
#or
PHP_VERSION=8.3 make all
```
For repeatability and consistency across different operating systems, we use the [3 Musketeers pattern](https://3musketeers.pages.dev/). If you're on Windows, it might be a good idea to use Git bash for following the steps below.
**Note: After cloning the repository, copy `.env.dist` to `.env`.**
Skipping the step above would result in a "`The "PHP_USER" variable is not set. Defaulting to a blank string`" warning
We use `docker` and `docker compose` to perform a lot of our static analysis and testing. If you're planning to develop for this library, it'll help to install
[docker engine](https://docs.docker.com/engine/install/) and the [compose plugin](https://docs.docker.com/compose/install/).
Development tasks are generally run through a `Makefile`. Running `make` or `make help` will list available targets.
## Workflow
### Pull Requests
To propose changes to the codebase, you need
to [open a pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request)
to the opentelemetry-php project.
After you open the pull request, the CI will run all the
associated [github actions](https://github.com/open-telemetry/opentelemetry-php/actions/workflows/php.yml).
To ensure your PR doesn't emit a failure with GitHub actions, it's recommended that you run the important CI tests locally with the following command:
```bash
make all # composer update, then run all checks
make all-lowest # composer update to lowest dependencies, then run all checks
```
This does the following things:
* Installs/updates all the required dependencies for the project
* Uses [Rector](https://github.com/rectorphp/rector) to refactor your code according to our standards.
* Uses [php-cs-fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) to style your code using our style preferences.
* Uses [Deptrac](https://github.com/qossmic/deptrac) to check for dependency violations inside our code base
* Makes sure the composer files for the different components are valid
* Runs all of our [phpunit](https://phpunit.de/) unit tests.
* Performs static analysis with [Phan](https://github.com/phan/phan), [Psalm](https://psalm.dev/)
and [PHPStan](https://phpstan.org/user-guide/getting-started)
## Local Run/Build
To ensure you have all the correct packages installed locally in your dev environment, you can run
```bash
make install
```
This will install all the library dependencies to
the `/vendor` directory.
To update these dependencies, you can run
```bash
make update
```
To downgrade to the lowest dependencies, you can run
```shell
make update-lowest
```
To run all checks without doing a composer update:
```shell
make all-checks
```
## Testing
To make sure the tests in this repo work as you expect, you can use the included docker test wrapper.
To run the test suite, execute
```bash
make test
```
This will output the test output as well as a test coverage analysis (text + html - see `tests/coverage/html`). Code
that doesn't pass our currently defined tests will emit a failure in CI
## Contributing Rules
Even though it may not be reflected everywhere in the codebase yet, we aim to provide software which is easy to read and change.
The methods described in Clean Code book(s) by Robert C. Martin (Uncle Bob) are a de facto industry standards nowadays.
Reading those books is highly recommended, however you can take a look at the examples given at [Clean Code PHP](https://github.com/jupeter/clean-code-php).
While we have no rule to strictly follow said methods and patterns, they are highly recommended as an orientation for
your pull requests and to be referenced in reviews.
We might add additional guidelines regarding for example testing in the future.
## Additional Information
### Automatic Refactoring and Upgrading
We use [Rector](https://github.com/rectorphp/rector) to automatically refactor our code according to given standards
and upgrade the code to supported PHP versions.
The associated configuration can be found [here](./.rector.php)
If you want to check what changes would be applied by rector, you can run:
```bash
make rector
```
This command will simply print out the changes `rector` would make without actually changing any code.
To refactor your code following our given standards, you can run:
```bash
make rector-write
```
This command applies the changes to the code base.
Make sure to run `make style` (see below) after running the `rector`command as the changes might not follow our coding standard.
### Styling
We use [PHP-CS-Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) for our code linting and standards fixer. The
associated configuration can be found [here](./.php-cs-fixer.php)
To ensure that your code follows our coding standards, you can run:
```bash
make style
```
This command executes a required check that also runs during CI. This process performs the required fixes and prints them
out. Code that doesn't meet the style pattern will emit a failure with GitHub actions.
### Static Analysis
We use [Phan](https://github.com/phan/phan/) for static analysis. Currently, our phan configuration is just a standard
default analysis configuration. You can use our phan docker wrapper to easily perform static analysis on your changes.
To run Phan, one can run the following command:
```bash
make phan
```
This process will return 0 on success. Usually this process is performed as part of a code checkin. This process runs
during CI and is a required check. Code that doesn't match the standards that we have defined in
our [phan config](https://github.com/open-telemetry/opentelemetry-php/blob/master/.phan/config.php) will emit a failure
in CI.
We also use [Psalm](https://psalm.dev/) as a second static analysis tool.
You can use our psalm docker wrapper to easily perform static analysis on your changes.
To run Psalm, one can run the following command:
```bash
make psalm
```
This process will return 0 on success. Usually this process is performed as part of a code checkin. This process runs
during CI and is a required check. Code that doesn't match the standards that we have defined in
our [psalm config](https://github.com/open-telemetry/opentelemetry-php/blob/main/psalm.xml.dist) will emit a failure in
CI.
We use [PHPStan](https://github.com/phpstan/phpstan) as our third tool for static analysis. You can use our PHPStan
docker wrapper to easily perform static analysis on your changes.
To perform static analysis with PHPStan run:
```bash
make phpstan
```
This process will return 0 on success. Usually this process is performed as part of a code checkin. This process runs
during CI and is a required check. Code that doesn't match the standards that we have defined in
our [PHPStan config](https://github.com/open-telemetry/opentelemetry-php/blob/main/phpstan.neon.dist) will emit a
failure in CI.
### Code Coverage
We use [codecov.io](https://about.codecov.io/) to track code coverage for this repo. This is configured in the [php.yaml github action](https://github.com/open-telemetry/opentelemetry-php/blob/main/.github/workflows/php.yml#L71-L72). We don't require a specific level of code coverage for PRs to pass - we just use this tool in order to understand how a PR will potentially change the amount of code coverage we have across the code base. This tool isn't perfect - sometimes we'll see small deltas in code coverage where there shouldn't be any - this is nothing to fret about.
If code coverage does decrease on a pull request, you will see a red X in the CI for the repo, but that's ok - the reviewer will use their judgement to determine whether or not we have sufficient code coverage for the change.
### Dependency Validation
To make sure the different components of the library are distributable as separate packages, we have to check
for dependency violations inside the code base. Dependencies must create a [DAC](https://en.wikipedia.org/wiki/Directed_acyclic_graph) in order to not create recursive dependencies.
For this purpose we use [Deptrac](https://github.com/qossmic/deptrac) and the respective configuration can be found
[here](./deptrac.yaml)
To validate the dependencies inside the code base, you can run:
```bash
make deptrac
```
This command will create an error for any violation of the defined dependencies. If you add new dependencies to the code base,
please configure them in the rector configuration.
## PhpMetrics
To generate a report showing a variety of metrics for the library and its classes, you can run:
```bash
make phpmetrics
```
This will generate a HTML PhpMetrics report in the `var/metrics` directory. Make sure to run `make test` before to
create the test log-file, used by the metrics report.
### Proto Generation
Our protobuf files are committed to the repository into the `/proto` folder. These are used in gRPC connections to the
upstream. These get updated when the [opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto)
repo has a meaningful update. The maintainer SIG is discussing a way to make this more automatic in the future.
To generate protobuf files for use with this repository, you can run the following command:
```bash
make protobuf
```
This will replace `proto/otel/Opentelemetry` and `proto/otel/GPBMetadata` with freshly generated code based on the
latest tag from `opentelemetry-proto`, which can then be committed.
### Semantic Conventions Generation
Autogenerated semantic convention files are committed to the repository in the `/src/SemConv` directory. These files are
updated manually when a new version of [semantic-conventions](https://github.com/open-telemetry/semantic-conventions) is
released.
```bash
SEMCONV_VERSION=1.8.0 make semconv
```
Run this command in the root of this repository.
### API Documentation
We use [phpDocumentor](https://phpdoc.org/) to automatically generate API documentation from DocBlocks in the code.
To generate a recent version of the API documentation, you can run:
```bash
make phpdoc
```
To preview the documentation and changes you might expect, you can run:
```bash
make phpdoc-preview
```
This will start a HTTP server running at <http://localhost:8080> serving the updated documentation files.
## Maintainers
- [Bob Strecansky](https://github.com/bobstrecansky), Intuit
- [Brett McBride](https://github.com/brettmc/), Deakin University
For more information about the maintainer role, see the [community repository](https://github.com/open-telemetry/community/blob/main/community-membership.md#maintainer).
## Approvers
- [Ago Allikmaa](https://github.com/agoallikmaa)
- [Cedriz Ziel](https://github.com/cedricziel)
- [Chris Lightfoot-Wild](https://github.com/ChrisLightfootWild)
For more information about the approver role, see the [community repository](https://github.com/open-telemetry/community/blob/main/community-membership.md#approver).
## Triagers
For more information about the triager role, see the [community repository](https://github.com/open-telemetry/community/blob/main/community-membership.md#triagers).
## Emeritus maintainers/approvers/triagers
- [Amber Zsistla](https://github.com/zsistla)
- [Beniamin Calota](https://github.com/beniamin)
- [Fahmy Mohammed](https://github.com/Fahmy-Mohammed)
- [Jodee Varney](https://github.com/jodeev)
- [Levi Morrison](https://github.com/morrisonlevi)
- [Przemek Delewski](https://github.com/pdelewski)
- [Timo Michna](https://github.com/tidal/)
For more information about the emeritus role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#emeritus-maintainerapprovertriager).
## Further Help
Most of our communication is done on CNCF Slack in the channel [otel-php](https://cloud-native.slack.com/archives/C01NFPCV44V).
To sign up, create a CNCF Slack account [here](http://slack.cncf.io/)
Our meetings are held weekly on zoom on Wednesdays at 10:30am PST / 1:30pm EST.
A Google calendar invite with the included zoom link can be found [here](https://calendar.google.com/event?action=TEMPLATE&tmeid=N2VtZXZmYnVmbzZkYjZkbTYxdjZvYTdxN21fMjAyMDA5MTZUMTczMDAwWiBrYXJlbnlyeHVAbQ&tmsrc=google.com_b79e3e90j7bbsa2n2p5an5lf60%40group.calendar.google.com&scp=ALL)
Our open issues can all be found in the [GitHub issues tab](https://github.com/open-telemetry/opentelemetry-php/issues). Feel free to reach out on Slack if you have any additional questions about these issues; we are always happy to talk through implementation details.
#### Thanks to all the people who already contributed!
<a href="https://github.com/open-telemetry/opentelemetry-php/graphs/contributors">
<img src="https://contributors-img.web.app/image?repo=open-telemetry/opentelemetry-php" />
</a>
Find more information about the triager role in the [community repository](https://github.com/open-telemetry/community/blob/master/community-membership.md#triager)

180
Context/Context.php Normal file
View File

@ -0,0 +1,180 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Context;
/**
* @template TContext of Context
*/
class Context
{
/**
* @var ContextKey|null
*/
protected $key;
/**
* @var mixed|null
*/
protected $value;
/**
* @var TContext|null
*/
protected $parent;
protected static $current_context = null;
/**
* This is a general purpose read-only key-value store. Read-only in the sense that adding a new value does not
* mutate the existing context, but returns a new Context which has the new value added.
*
* In practical terms, this is implemented as a linked list of Context instances, with each one holding a reference
* to the key object, the value that corresponds to the key, and an optional reference to the parent Context
* (i.e. the next link in the linked list chain)
*
* If you inherit from this class, you should "shadow" $parent into your subclass so that all operations give
* you back an instance of the same type that you are interacting with and different subclasses should NOT be
* treated as interoperable. i.e. you should NOT have a Context object chain with both Context instances interleaved
* with Baggage instances.
*
* @param ContextKey|null $key The key object. Should only be null when creating an "empty" context
* @param mixed|null $value
* @param TContext|null $parent Reference to the parent object
*/
final public function __construct(?ContextKey $key=null, $value=null, $parent=null)
{
$this->key = $key;
$this->value = $value;
$this->parent = $parent;
}
/**
* This adds a k/v pair to this Context. We do this by instantiating a new Context instance with the k/v and pass
* a reference to $this as the "parent" creating the linked list chain.
*
* @param ContextKey $key
* @param mixed $value
*
* @return Context a new Context containing the k/v
*/
public function set(ContextKey $key, $value)
{
return new static($key, $value, $this);
}
/**
* This is a static version of set().
* This is primarily useful when the caller doesn't already have a reference to a Context that they want to mutate.
*
* There are two ways to call this function.
* 1) With a $parent parameter:
* Context::setValue($key, $value, $ctx) is functionally equivalent to $ctx->set($key, $value)
* 2) Without a $parent parameter:
* In this scenario, setValue() will use the `$current_context` reference as supplied by `getCurrent()`
* `getCurrent()` will always return a valid Context. If one does not exist at the global scope,
* an "empty" context will be created.
*
* @param ContextKey $key
* @param mixed $value
* @param Context|null $parent
*
* @return Context a new Context containing the k/v
*/
public static function setValue(ContextKey $key, $value, $parent=null)
{
if (null === $parent) {
return static::$current_context = new static($key, $value, static::getCurrent());
}
return new static($key, $value, $parent);
}
/**
* Fetch a value from the Context given a key value.
*
* @param ContextKey $key
*
* @throws ContextValueNotFoundException
* @return mixed
* @suppress PhanUndeclaredClassMethod
*/
public function get(ContextKey $key)
{
if ($this->key === $key) {
return $this->value;
}
if (null === $this->parent) {
throw new ContextValueNotFoundException();
}
return $this->parent->get($key);
}
/**
* Static version of get()
* This is primarily useful when the caller doesn't already have a reference to the Context that they want to mutate.
* This will operate on the "current" global context in that scenario.
*
* There are two ways to call this function:
* 1) With a $ctx value:
* Context::getValue($key, $ctx) is functionally equivalent to $ctx->get($key)
* 2) Without a $ctx value:
* This will fetch the "current" Context if one exists or create one if not, then attempt to get the value from it.
*
* @param ContextKey $key
* @param Context|null $ctx
*
* @throws ContextValueNotFoundException
* @return mixed
*/
public static function getValue(ContextKey $key, $ctx=null)
{
$ctx = $ctx ?? static::getCurrent();
return $ctx->get($key);
}
/**
* @return Context
*/
public static function getCurrent()
{
if (null === static::$current_context) {
static::$current_context = new static();
}
return static::$current_context;
}
/**
* This will set the given Context to be the "current" one. We return a token which can be passed to `detach()` to
* reset the Current Context back to the previous one.
*
* @param Context $ctx
*
* @return callable token for resetting the $current_context back
*/
public static function attach($ctx): callable
{
$former_ctx = static::$current_context;
static::$current_context = $ctx;
return function () use ($former_ctx) {
return $former_ctx;
};
}
/**
* Given a token, the current context will be set back to the one prior to the token being generated.
*
* @param callable $token
*
* @return Context
*/
public static function detach(callable $token)
{
return static::$current_context = call_user_func($token);
}
}

23
Context/ContextKey.php Normal file
View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Context;
class ContextKey
{
/**
* @var string|null
*/
private $name;
public function __construct(?string $name=null)
{
$this->name = $name;
}
public function name(): ?string
{
return $this->name;
}
}

View File

@ -4,9 +4,6 @@ declare(strict_types=1);
namespace OpenTelemetry\Context;
/**
* @template-covariant T
*/
interface ContextKeyInterface
class ContextValueNotFoundException extends \UnexpectedValueException
{
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Context;
trait ContextValueTrait
{
/**
* @param Context $context
* @return mixed|null
* @phan-suppress PhanAbstractStaticMethodCallInTrait
*/
public static function extract(Context $context)
{
try {
return $context->get(static::getContextKey());
} catch (ContextValueNotFoundException $e) {
return null;
}
}
/**
* @param mixed $value
* @param Context $context
* @return Context
* @phan-suppress PhanAbstractStaticMethodCallInTrait
*/
public static function insert($value, Context $context): Context
{
return $context->set(static::getContextKey(), $value);
}
/**
* @param mixed $value
* @return Scope
* @phan-suppress PhanAbstractStaticMethodCallInTrait
*/
public static function setCurrent($value): Scope
{
$context = Context::getCurrent()->set(static::getContextKey(), $value);
return new Scope(Context::attach($context));
}
/**
* @return mixed|null
* @phan-suppress PhanAbstractStaticMethodCallInTrait
*/
public static function getCurrent()
{
try {
return Context::getCurrent()->get(static::getContextKey());
} catch (ContextValueNotFoundException $e) {
return null;
}
}
abstract protected static function getContextKey(): ContextKey;
}

21
Context/Scope.php Normal file
View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Context;
class Scope
{
/** @var callable Context token, result of Context::attach() */
private $contextToken;
public function __construct(callable $contextToken)
{
$this->contextToken = $contextToken;
}
public function close(): void
{
Context::detach($this->contextToken);
}
}

144
Makefile
View File

@ -1,114 +1,44 @@
include .env
DC_RUN_PHP = docker-compose run --rm php
PHP_VERSION ?= 8.1
DOCKER_COMPOSE ?= docker compose
DC_RUN_PHP = $(DOCKER_COMPOSE) run --rm php
install:
$(DC_RUN_PHP) composer install
update:
$(DC_RUN_PHP) composer update
test:
$(DC_RUN_PHP) php ./vendor/bin/phpunit --colors=always --coverage-text --testdox --coverage-clover coverage.clover
phan:
$(DC_RUN_PHP) env PHAN_DISABLE_XDEBUG_WARN=1 php ./vendor/bin/phan
psalm:
$(DC_RUN_PHP) php ./vendor/bin/psalm
psalm-info:
$(DC_RUN_PHP) php ./vendor/bin/psalm --show-info=true
phpstan:
$(DC_RUN_PHP) php ./vendor/bin/phpstan analyse
trace examples: FORCE
docker-compose up -d --remove-orphans
$(DC_RUN_PHP) php ./examples/AlwaysOnZipkinExample.php
$(DC_RUN_PHP) php ./examples/AlwaysOffTraceExample.php
$(DC_RUN_PHP) php ./examples/AlwaysOnJaegerExample.php
# The following examples do not use the DC_RUN_PHP global because they need environment variables.
docker-compose run -e NEW_RELIC_ENDPOINT -e NEW_RELIC_INSERT_KEY --rm php php ./examples/AlwaysOnNewrelicExample.php
docker-compose run -e NEW_RELIC_ENDPOINT -e NEW_RELIC_INSERT_KEY --rm php php ./examples/AlwaysOnZipkinToNewrelicExample.php
docker-compose stop
collector:
docker-compose -f docker-compose-collector.yaml up -d --remove-orphans
docker-compose -f docker-compose-collector.yaml run -e OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317 --rm php php ./examples/AlwaysOnOTLPGrpcExample2.php
docker-compose -f docker-compose-collector.yaml stop
.DEFAULT_GOAL : help
.PHONY: deptrac
help: ## Show this help
@printf "\033[33m%s:\033[0m\n" 'Available commands'
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_-]+:.*?## / {printf " \033[32m%-18s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
all: update all-checks ## Update to latest and run all checks
all-lowest: update-lowest all-checks ## Update to lowest dependencies and run all checks
all-checks: rector style deptrac packages-composer phan psalm phpstan test ## Run all checks
pull: ## Pull latest developer image
$(DOCKER_COMPOSE) pull php
build: ## Build developer image locally
docker build docker/ --build-arg PHP_VERSION=${PHP_VERSION} -t ghcr.io/open-telemetry/opentelemetry-php/opentelemetry-php-base:${PHP_VERSION}
install: ## Install dependencies
$(DC_RUN_PHP) env XDEBUG_MODE=off composer install
update: ## Update dependencies
$(DC_RUN_PHP) env XDEBUG_MODE=off composer update
update-lowest: ## Update dependencies to lowest supported versions
$(DC_RUN_PHP) env XDEBUG_MODE=off composer update --prefer-lowest
test: test-unit test-integration ## Run unit and integration tests
test-unit: ## Run unit tests
$(DC_RUN_PHP) env XDEBUG_MODE=coverage vendor/bin/phpunit --testsuite unit --colors=always
test-integration: ## Run integration tests
$(DC_RUN_PHP) env XDEBUG_MODE=off vendor/bin/phpunit --testsuite integration --colors=always
test-verbose: ## Run unit tests with verbose (testdox) output
$(DC_RUN_PHP) env XDEBUG_MODE=coverage vendor/bin/phpunit --testsuite unit --colors=always --testdox --coverage-clover coverage.clover --coverage-html=tests/coverage/html --log-junit=junit.xml
test-coverage: ## Run units tests and generate code coverage
$(DC_RUN_PHP) env XDEBUG_MODE=coverage vendor/bin/phpunit --testsuite unit --coverage-html=tests/coverage/html
test-compliance: ## Run compliance tests
$(DC_RUN_PHP) env XDEBUG_MODE=coverage vendor/bin/phpunit --group compliance
test-trace-compliance: ## Run trace compliance tests
$(DC_RUN_PHP) env XDEBUG_MODE=coverage vendor/bin/phpunit --group trace-compliance
phan: ## Run phan
$(DC_RUN_PHP) env XDEBUG_MODE=off env PHAN_DISABLE_XDEBUG_WARN=1 vendor-bin/phan/vendor/bin/phan
psalm: ## Run psalm
$(DC_RUN_PHP) env XDEBUG_MODE=off vendor-bin/psalm/vendor/bin/psalm --threads=1 --no-cache
psalm-info: ## Run psalm and show info
$(DC_RUN_PHP) env XDEBUG_MODE=off vendor-bin/psalm/vendor/bin/psalm --show-info=true --threads=1
phpdoc: ## Run phpdoc
$(DOCKER_COMPOSE) -f docker-compose.phpDocumentor.yaml run --rm phpdoc
phpdoc-preview:
$(DOCKER_COMPOSE) -f docker-compose.phpDocumentor.yaml run --service-ports --rm preview
phpstan: ## Run phpstan
$(DC_RUN_PHP) env XDEBUG_MODE=off vendor/bin/phpstan analyse --memory-limit=256M
infection: ## Run infection (mutation testing)
$(DC_RUN_PHP) env XDEBUG_MODE=coverage php -d memory_limit=1024M vendor-bin/infection/vendor/bin/infection --threads=max
packages-composer: ## Validate composer packages
$(DC_RUN_PHP) env XDEBUG_MODE=off vendor/bin/otel packages:composer:validate
benchmark: ## Run phpbench
$(DC_RUN_PHP) env XDEBUG_MODE=off vendor-bin/phpbench/vendor/bin/phpbench run --report=default
phpmetrics: ## Run php metrics
$(DC_RUN_PHP) env XDEBUG_MODE=off vendor-bin/phpmetrics/vendor/bin/phpmetrics --config=./phpmetrics.json --junit=junit.xml
smoke-test-examples: smoke-test-isolated-examples smoke-test-exporter-examples smoke-test-collector-integration smoke-test-prometheus-example ## Run smoke test examples
smoke-test-isolated-examples: ## Run smoke test isolated examples
$(DC_RUN_PHP) php ./examples/traces/getting_started.php
$(DC_RUN_PHP) php ./examples/traces/features/batch_exporting.php
$(DC_RUN_PHP) php ./examples/traces/features/concurrent_spans.php
$(DC_RUN_PHP) php ./examples/traces/features/configuration_from_environment.php
$(DC_RUN_PHP) php ./examples/traces/features/creating_a_new_trace_in_the_same_process.php
$(DC_RUN_PHP) php ./examples/traces/features/resource_detectors.php
$(DC_RUN_PHP) php ./examples/traces/features/span_resources.php
$(DC_RUN_PHP) php ./examples/traces/troubleshooting/air_gapped_trace_debugging.php
$(DC_RUN_PHP) php ./examples/traces/troubleshooting/logging_of_span_data.php
$(DC_RUN_PHP) php ./examples/traces/troubleshooting/setting_up_logging.php
smoke-test-exporter-examples: FORCE ## Run (some) exporter smoke test examples
# Note this does not include every exporter at the moment
$(DOCKER_COMPOSE) up -d --remove-orphans
$(DC_RUN_PHP) php ./examples/traces/exporters/zipkin.php
$(DC_RUN_PHP) php ./examples/traces/features/parent_span_example.php
smoke-test-collector-integration: ## Run smoke test collector integration
$(DOCKER_COMPOSE) -f docker-compose.collector.yaml up -d --remove-orphans
sleep 5
$(DOCKER_COMPOSE) -f docker-compose.collector.yaml run --rm php php ./examples/traces/exporters/otlp_grpc.php
$(DOCKER_COMPOSE) -f docker-compose.collector.yaml run --rm php php ./examples/traces/exporters/otlp_http.php
$(DOCKER_COMPOSE) -f docker-compose.collector.yaml stop
smoke-test-collector-metrics-integration:
$(DOCKER_COMPOSE) -f docker-compose.collector.yaml up -d --force-recreate collector
COMPOSE_IGNORE_ORPHANS=TRUE $(DOCKER_COMPOSE) -f docker-compose.yaml run --rm php php ./examples/metrics/features/exporters/otlp_http.php
$(DOCKER_COMPOSE) -f docker-compose.collector.yaml logs collector
$(DOCKER_COMPOSE) -f docker-compose.collector.yaml stop collector
smoke-test-prometheus-example: metrics-prometheus-example stop-prometheus
metrics-prometheus-example:
@$(DOCKER_COMPOSE) -f docker-compose.prometheus.yaml -p opentelemetry-php_metrics-prometheus-example up -d web
# This is slow because it's building the image from scratch (and parts of that, like installing the gRPC extension, are slow)
@$(DOCKER_COMPOSE) -f docker-compose.prometheus.yaml -p opentelemetry-php_metrics-prometheus-example run --rm php php examples/metrics/prometheus/prometheus_metrics_example.php
@docker-compose -f docker-compose.prometheus.yaml up -d web
@docker-compose -f docker-compose.prometheus.yaml run php-prometheus php /var/www/public/examples/prometheus/PrometheusMetricsExample.php
stop-prometheus:
@$(DOCKER_COMPOSE) -f docker-compose.prometheus.yaml -p opentelemetry-php_metrics-prometheus-example stop
fiber-ffi-example:
@$(DOCKER_COMPOSE) -f docker-compose.fiber-ffi.yaml -p opentelemetry-php_fiber-ffi-example up -d web
protobuf: ## Generate protobuf files
./script/proto_gen.sh
bash: ## bash shell into container
@docker-compose -f docker-compose.prometheus.yaml stop
proto:
@docker-compose -f docker-compose.proto.yaml up proto
bash:
$(DC_RUN_PHP) bash
style: ## Run style check/fix
$(DC_RUN_PHP) env XDEBUG_MODE=off env PHP_CS_FIXER_IGNORE_ENV=1 vendor-bin/php-cs-fixer/vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --using-cache=no -vvv
rector-write: ## Run rector
$(DC_RUN_PHP) env XDEBUG_MODE=off vendor-bin/rector/vendor/bin/rector process
rector: ## Run rector (dry-run)
$(DC_RUN_PHP) env XDEBUG_MODE=off vendor-bin/rector/vendor/bin/rector process --dry-run
deptrac: ## Run deptrac
$(DC_RUN_PHP) env XDEBUG_MODE=off vendor-bin/deptrac/vendor/bin/deptrac --formatter=table --report-uncovered --no-cache
style:
$(DC_RUN_PHP) php ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --stop-on-violation --using-cache=no -vvv
w3c-test-service:
@$(DOCKER_COMPOSE) -f docker-compose.w3cTraceContext.yaml run --rm php ./tests/TraceContext/W3CTestService/trace-context-test.sh
semconv: ## Generate semconv files
./script/semantic-conventions/semconv.sh
split: ## Run git split
$(DOCKER_COMPOSE) -f docker/gitsplit/docker-compose.yaml --env-file ./.env up
@docker-compose -f docker-compose.w3cTraceContext.yaml run --rm php ./tests/TraceContext/W3CTestService/symfony-setup
FORCE:

201
README.md
View File

@ -1,56 +1,189 @@
# OpenTelemetry for PHP
# OpenTelemetry php library
![CI Build](https://github.com/open-telemetry/opentelemetry-php/workflows/PHP%20QA/badge.svg)
![CI Build](https://github.com/open-telemetry/opentelemetry-php/workflows/PHP%20Composer/badge.svg)
[![codecov](https://codecov.io/gh/open-telemetry/opentelemetry-php/branch/master/graph/badge.svg)](https://codecov.io/gh/open-telemetry/opentelemetry-php)
[![Slack](https://img.shields.io/badge/slack-@cncf/otel--php-brightgreen.svg?logo=slack)](https://cloud-native.slack.com/archives/D03FAB6GN0K)
This is the **[monorepo](https://en.wikipedia.org/wiki/Monorepo)** for the **main** components of [OpenTelemetry](https://opentelemetry.io/) for PHP.
## Current Project Status
![Current Version](https://img.shields.io/github/v/tag/open-telemetry/opentelemetry-php)
## Documentation
This project currently lives in a pre-alpha status. Our current release is not production ready; it has been created in order to receive feedback from the community.
Please read the official documentation: https://opentelemetry.io/docs/instrumentation/php/
We attempt to keep the [OpenTelemetry Specification Matrix](https://github.com/open-telemetry/opentelemetry-specification/blob/master/spec-compliance-matrix.md) up to date in order to show which features are available and which have not yet been implemented.
API Documentation is available here: https://open-telemetry.github.io/opentelemetry-php/
If you find an inconsistency in the data in the matrix vs. the data in this repository, please let us know in our slack channel and we'll get it rectified.
## Communication
Most of our communication is done on CNCF Slack, in the [otel-php](https://cloud-native.slack.com/archives/C01NFPCV44V) channel. To sign up, create a CNCF slack account here http://slack.cncf.io/
## Packages and versions
Our meetings are held weekly on zoom on Wednesdays at 10:30am PST / 1:30pm EST.
A Google calendar invite with the included zoom link can be found [here](https://calendar.google.com/event?action=TEMPLATE&tmeid=N2VtZXZmYnVmbzZkYjZkbTYxdjZvYTdxN21fMjAyMDA5MTZUMTczMDAwWiBrYXJlbnlyeHVAbQ&tmsrc=google.com_b79e3e90j7bbsa2n2p5an5lf60%40group.calendar.google.com&scp=ALL)
| Package | Latest |
|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| API | [![Latest Stable Version](https://poser.pugx.org/open-telemetry/api/v/stable)](https://packagist.org/packages/open-telemetry/api/) [![Latest Unstable Version](https://poser.pugx.org/open-telemetry/api/v/unstable)](https://packagist.org/packages/open-telemetry/api/) |
| SDK | [![Latest Stable Version](https://poser.pugx.org/open-telemetry/sdk/v/stable)](https://packagist.org/packages/open-telemetry/sdk/) [![Latest Unstable Version](https://poser.pugx.org/open-telemetry/sdk/v/unstable)](https://packagist.org/packages/open-telemetry/sdk/) |
| Context | [![Latest Stable Version](https://poser.pugx.org/open-telemetry/context/v/stable)](https://packagist.org/packages/open-telemetry/context/) [![Latest Unstable Version](https://poser.pugx.org/open-telemetry/context/v/unstable)](https://packagist.org/packages/open-telemetry/context/) |
| Semantic Conventions | [![Latest Stable Version](https://poser.pugx.org/open-telemetry/sem-conv/v/stable)](https://packagist.org/packages/open-telemetry/sem-conv/) [![Latest Unstable Version](https://poser.pugx.org/open-telemetry/sem-conv/v/unstable)](https://packagist.org/packages/open-telemetry/sem-conv/) |
| OTLP Exporter | [![Latest Stable Version](https://poser.pugx.org/open-telemetry/exporter-otlp/v/stable)](https://packagist.org/packages/open-telemetry/exporter-otlp/) [![Latest Unstable Version](https://poser.pugx.org/open-telemetry/exporter-otlp/v/unstable)](https://packagist.org/packages/open-telemetry/exporter-otlp/) |
| gRPC Transport | [![Latest Stable Version](https://poser.pugx.org/open-telemetry/transport-grpc/v/stable)](https://packagist.org/packages/open-telemetry/transport-grpc/) [![Latest Unstable Version](https://poser.pugx.org/open-telemetry/transport-grpc/v/unstable)](https://packagist.org/packages/open-telemetry/transport-grpc/) |
| OTLP Protobuf Files | [![Latest Stable Version](https://poser.pugx.org/open-telemetry/gen-otlp-protobuf/v/stable)](https://packagist.org/packages/open-telemetry/gen-otlp-protobuf/) [![Latest Unstable Version](https://poser.pugx.org/open-telemetry/gen-otlp-protobuf/v/unstable)](https://packagist.org/packages/open-telemetry/gen-otlp-protobuf/) |
| B3 Propagator | [![Latest Stable Version](https://poser.pugx.org/open-telemetry/extension-propagator-b3/v/stable)](https://packagist.org/packages/open-telemetry/extension-propagator-b3/) [![Latest Unstable Version](https://poser.pugx.org/open-telemetry/extension-propagator-b3/v/unstable)](https://packagist.org/packages/open-telemetry/extension-propagator-b3/) |
Our open issues can all be found in the [github issues tab](https://github.com/open-telemetry/opentelemetry-php/issues). Feel free to reach out on Slack if you have any additional questions about these issues; we are always happy to talk through implementation details.
Releases for both this repository and [contrib](https://github.com/open-telemetry/opentelemetry-php-contrib) are
based on read-only [git subtree splits](https://github.com/splitsh/lite) from our monorepo. You should refer to
[packagist.org](https://packagist.org/packages/open-telemetry/) for all packages, their versions and details.
## Installation
The recommended way to install the library is through [Composer](http://getcomposer.org):
You can also look at the read-only repositories, which live in the
[opentelemetry-php](https://github.com/opentelemetry-php) organization.
1.) Install the composer package using [Composer's installation instructions](https://getcomposer.org/doc/00-intromd#installation-linux-unix-macos).
## Contributing
2.) Add
```bash
"minimum-stability": "dev"
```
[![GitHub repo Good Issues for newbies](https://img.shields.io/github/issues/open-telemetry/opentelemetry-php/good%20first%20issue?style=flat&logo=github&logoColor=green&label=Good%20First%20issues)](https://github.com/open-telemetry/opentelemetry-php/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) [![GitHub Help Wanted issues](https://img.shields.io/github/issues/open-telemetry/opentelemetry-php/help%20wanted?style=flat&logo=github&logoColor=b545d1&label=%22Help%20Wanted%22%20issues)](https://github.com/open-telemetry/opentelemetry-php/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) [![GitHub Help Wanted PRs](https://img.shields.io/github/issues-pr/open-telemetry/opentelemetry-php/help%20wanted?style=flat&logo=github&logoColor=b545d1&label=%22Help%20Wanted%22%20PRs)](https://github.com/open-telemetry/opentelemetry-php/pulls?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) [![GitHub repo Issues](https://img.shields.io/github/issues/open-telemetry/opentelemetry-php?style=flat&logo=github&logoColor=red&label=Issues)](https://github.com/open-telemetry/opentelemetry-php/issues?q=is%3Aopen)
To your project's `composer.json` file, as this utility has not reached a stable release status yet.
We would love to have you on board, please see our [Contributing README](./CONTRIBUTING.md)
2.) Install the dependency with composer:
## Specification conformance
```bash
$ composer require open-telemetry/opentelemetry
```
We attempt to keep the [OpenTelemetry Specification Matrix](https://github.com/open-telemetry/opentelemetry-specification/blob/master/spec-compliance-matrix.md) up to date in order to show which features are available and which have not yet been implemented.
## Development
We use `docker` and `docker-compose` to perform a lot of our static analysis and testing.
If you find an inconsistency in the data in the matrix, please let us know in our slack channel and we'll get it rectified.
If you're planning to develop for this library, it'll help to install `docker engine` and `docker-compose`.
## Backwards compatibility
You can find installation instructions for these packages can be found [here](https://docs.docker.com/install/), under the `Docker Engine` and `Docker Compose` submenus respectively.
See [compatibility readme](src/SDK/Common/Dev/Compatibility/README.md).
To ensure you have all the correct packages installed locally in your dev environment, you can run
```bash
make install
```
From your bash compatible shell. This will install all of the necessary vendored libraries that the project uses to
the
`/vendor` directory.
To update these dependencies, you can run
```bash
make update
```
In order to update all the vendored libraries in the `/vendor` directory.
## Pull Requests
Once you've made the update to the codebase that you'd like to submit, you may [create a pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request) to the opentelemetry-php project.
After you open the pull request, the CI/CD pipeline will run all of the associated [github actions](https://github.com/open-telemetry/opentelemetry-php/actions/workflows/php.yml).
You can simulate the important github actions locally before you submit your PR by running the following command:
```bash
make install && make update && make style && make test && make phan && make psalm && make phpstan
```
from your bash compatible shell. This does the following things:
* Installs all the required dependencies for the project and ensures they are up to date
* Uses [php-cs-fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) to style your code using our style preferences.
* Runs all of our [phpunit](https://phpunit.de/) unit tests.
* Performs static analysis with [Phan](https://github.com/phan/phan), [Psalm](https://psalm.dev/) and [PHPStan](https://phpstan.org/user-guide/getting-started)
## Proto Generation
Our proto files are committed to the repository into the `/proto` folder. These are used in gRPC connections to the
upstream. These get updated when the [opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto)
repo has a meaningful update. The maintainer SIG is discussing a way to make this more automatic in the future.
If you'd like to generate proto files for use with this repository, one can run the following command:
```bash
make proto
```
From your bash compatible shell in the root of this directory. This wil create a `/proto` folder in the root
directory of the
repository.
## Styling
We use [PHP-CS-Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) for our code linting and standards fixer. The associated configuration for this standards fixer can be found in the root of the repository [here](https://github.com/open-telemetry/opentelemetry-php/blob/master/.php_cs)
To ensure that your code is stylish, you can run the follwing command:
```bash
make style
```
from your bash compatible shell. This process will print out the fixes that it is making to your
associated files. Usually this process is performed as part of a code checkin. This process runs during CI and is a required check. Code that doesn't follow this style pattern will emit a failure in CI.
## Static Analysis
We use [Phan](https://github.com/phan/phan/) for static analysis. Currently our phan configuration is just a standard default analysis configuration. You can use our phan docker wrapper to easily perform static analysis on your changes.
To run Phan, one can run the following command:
```bash
make phan
```
from your bash compatible shell.
This process will return 0 on success.
Usually this process is performed as part of a code checkin. This process runs during CI and is a required check. Code that doesn't match the standards that we have defined in our [phan config](https://github.com/open-telemetry/opentelemetry-php/blob/master/.phan/config.php) will emit a failure in CI.
We also use [Psalm](https://psalm.dev/) as a second static analysis tool.
You can use our psalm docker wrapper to easily perform static analysis on your changes.
To run Psalm, one can run the following command:
```bash
make psalm
```
from your bash compatible shell. This process will return 0 on success. Usually this process is performed as part of a code checkin. This process runs during CI and is a required check. Code that doesn't match the standards that we have defined in our [psalm config](https://github.com/open-telemetry/opentelemetry-php/blob/main/psalm.xml.dist) will emit a failure in CI.
We use [PHPStan](https://github.com/phpstan/phpstan) as our third tool for static analysis.
You can use our PHPStan docker wrapper to easily perform static analysis on your changes.
Execute `make phpstan` from your bash compatible shell. This process will return 0 on success. Usually this process is
performed as part of a code checkin. This process runs during CI and is a required check. Code that doesn't match the
standards that we have defined in
our [PHPStan config](https://github.com/open-telemetry/opentelemetry-php/blob/main/phpstan.neon.dist) will emit a failure
in CI.
## Testing
To make sure the tests in this repo work as you expect, you can use the included docker test wrapper.
To run the test suite, execute
```bash
make test
```
from your bash compatible shell. This will output the test output as well
as a test coverage analysis. Code that doesn't pass our currently defined tests will emit a failure in CI
## Examples
### Trace
You can use the [examples/AlwaysOnZipkinExample.php](/examples/AlwaysOnZipkinExample.php) file to test out the reference implementation we have for zipkin. This example perfoms a sample trace with a grouping of 5 spans and POSTs the result to a local zipkin instance.
You can also use the [examples/AlwaysOnJaegerExample.php](/examples/AlwaysOnJaegerExample.php) file to test out the reference implementation we have for jaegar. This example perfoms a sample trace with a grouping of 5 spans and POSTs the result to a local jaegar instance.
You can use the [examples/AlwaysOnZipkinToNewrelicExample.php](/examples/AlwaysOnZipkinToNewrelicExample.php) file to test out the reference implementation we have for zipkin to Newrelic. This example perfoms a sample trace with spans and POSTs the result to a Newrelic endpoint. This requires a license key (free accounts available) to be set in the environment (NEW_RELIC_INSERT_KEY) to authenticate to the backend.
You can use the [examples/AlwaysOnNewrelicExample.php](/examples/AlwaysOnNewrelicExample.php) file to test out the reference implementation we have for Newrelic. This example perfoms a sample trace with spans and POSTs the result to a Newrelic endpoint. This requires a license key (free accounts available) set in the environment (NEW_RELIC_INSERT_KEY) to authenticate to the backend.
The PHP for all examples should execute by itself (if you have a zipkin or jaegar instance running on localhost), but if you'd like a no-fuss way to test this out with docker and docker-compose, you can perform the following simple steps:
1.) Install the necessary dependencies by running `make install`. This will install the composer dependencies and store them in `/vendor`
2.) Execute the example trace using `make trace examples`.
Exported spans can be seen in zipkin at [http://127.0.0.1:9411](http://127.0.0.1:9411)
Exported spans can also be seen in jaeger at [http://127.0.0.1:16686](http://127.0.0.1:16686)
### Metrics
You can use the [examples/prometheus/PrometheusMetricsExample.php](/examples/prometheus/PrometheusMetricsExample.php) file to test out the reference implementation we have. This example will create a counter that will be scraped by local Prometheus instance.
The easy way to test the example out with docker and docker-compose is:
1.) Run `make metrics-prometheus-example`. Make sure that local ports 8080, 6379 and 9090 are available.
2.) Open local Prometheus instance: http://localhost:9090
3.) Go to Graph section, type "opentelemetry_prometheus_counter" in the search field or select it in the dropdown menu. You will see the counter value. Every other time you run `make metrics-prometheus-example` will increment the counter but remember that Prometheus scrapes values once in 10 seconds.
4.) In order to stop docker containers for this example just run `make stop-prometheus`
## User Integration Guides
* [Integrating OpenTelemetry PHP into Laravel Applications](./docs/laravel-integration.md)
* [Integrating OpenTelemetry PHP into Symfony Applications](./docs/symfony-integration.md)
## Versioning
OpenTelemetry for PHP aims to support all officially supported PHP versions according to https://www.php.net/supported-versions.php, and
support will be dropped for PHP versions within 12 months of that version going _End of life_.
Versioning rationale can be found in the [Versioning Documentation](/docs/versioning.md)

24
api/Baggage.php Normal file
View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry;
use OpenTelemetry\Context\ContextKey;
interface Baggage
{
public function getCorrelations(); // TODO
public function get(ContextKey $key);
public static function getValue(ContextKey $key, ?Baggage $ctx);
public function set(ContextKey $key, $value): Baggage;
public static function setValue(ContextKey $key, $value, ?Baggage $parent = null): Baggage;
public function removeCorrelation(): Baggage;
public function clearCorrelations(); // TODO
}

33
api/Metrics/Counter.php Normal file
View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Metrics;
interface Counter extends Metric
{
/**
* Adds value to the counter
*
* @access public
* @param int $value
* @return self
*/
public function add(int $value): Counter;
/**
* Increments value
*
* @access public
* @return self
*/
public function increment(): Counter;
/**
* Gets the value
*
* @access public
* @return int
*/
public function getValue(): int;
}

26
api/Metrics/Exporter.php Normal file
View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Metrics;
interface Exporter
{
/**
* Possible return values as outlined in the OpenTelemetry spec
*/
const SUCCESS = 0;
const FAILED_NOT_RETRYABLE = 1;
const FAILED_RETRYABLE = 2;
/**
* export.
*
* @access public
* @param iterable<Metric> $metrics
* @return int
*
* todo Should we pass a result callback in the 2nd parameter like in JavaScript implementation?
*/
public function export(iterable $metrics): int;
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Metrics;
interface LabelableMetric extends Metric
{
/**
* Get $labels
* todo: we will probably need a class Label and a typed collection for labels
*
* @return array<string>
*/
public function getLabels(): array;
/**
* Set $labels
*
* @param array<string> $labels
* @return self
*/
public function setLabels(array $labels);
/**
* Set $labels
*
* @param string $label
* @return self
*/
public function addLabel(string $label);
}

52
api/Metrics/Meter.php Normal file
View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Metrics;
interface Meter
{
/**
* Returns the meter name.
*
* @return string
*/
public function getName(): string;
/**
* Returns the meter version.
*
* @return string Metric version
*/
public function getVersion(): string;
/**
* Creates a Counter metric instrument.
*
* @param string $name (required) - Counter name
* @param string $description (optional) - Counter description
*
* @return Counter
*/
public function newCounter(string $name, string $description): Counter;
/**
* Creates an UpDownCounter metric instrument.
*
* @param string $name (required) - UpDownCounter name
* @param string $description (optional) - UpDownCounter description
*
* @return UpDownCounter
*/
public function newUpDownCounter(string $name, string $description): UpDownCounter;
/**
* Creates a ValueRecorder metric instrument.
*
* @param string $name (required) - ValueRecorder name
* @param string $description (optional) - ValueRecorder description
*
* @return ValueRecorder
*/
public function newValueRecorder(string $name, string $description): ValueRecorder;
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Metrics;
interface MeterProvider
{
/**
* @access public
* @param string $name - (required) - This name must identify the instrumentation library
* (e.g. io.opentelemetry.contrib.mongodb) and not the instrumented library.
* In case an invalid name (null or empty string) is specified, a working default Meter implementation is returned
* as a fallback rather than returning null or throwing an exception.
* A MeterProvider could also return a no-op Meter here if application owners configure the SDK to suppress
* telemetry produced by this library.
* @param ?string $version - (optional) - Specifies the version of the instrumentation library (e.g. semver:1.0.0)
* @return Meter
*/
public function getMeter(string $name, ?string $version = null): Meter;
}

32
api/Metrics/Metric.php Normal file
View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Metrics;
interface Metric
{
/**
* Returns metric's name
*
* @access public
* @return string
*/
public function getName(): string;
/**
* Returns metric's description
*
* @access public
* @return string
*/
public function getDescription(): string;
/**
* Returns metric's type
*
* @access public
* @return int
*/
public function getType(): int;
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Metrics;
interface MetricKind
{
public const COUNTER = 1;
public const UP_DOWN_COUNTER = 2;
public const VALUE_RECORDER = 3;
public const SUM_OBSERVER = 4;
public const UP_DOWN_SUM_OBSERVER = 4;
public const VALUE_OBSERVER = 5;
public const TYPES = [
self::COUNTER,
self::UP_DOWN_COUNTER,
self::VALUE_RECORDER,
self::SUM_OBSERVER,
self::UP_DOWN_SUM_OBSERVER,
self::VALUE_OBSERVER,
];
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Metrics;
/*
* Name: UpDownCounter
* Instrument kind : Synchronous additive
* Function(argument) : Add(increment) where increment is a numeric value
* Default aggregation : Sum
* Notes : Per-request, part of a non-monotonic sum
*
* UpDownCounter supports negative increments. This makes UpDownCounter
* not useful for computing a rate aggregation. It aggregates a Sum,
* only the sum is non-monotonic. It is generally useful for capturing changes
* in an amount of resources used, or any quantity that rises and falls during
* a request.
*
*/
interface UpDownCounter
{
/**
* Updates counter value with the positive or negative int that is passed in.
*
* @access public
* @param int|float $increment
* @return int returns the non-monotonic sum
*/
public function add($increment) : int;
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Metrics;
/*
* Name: UpDownSumObserver
* Instrument kind : Asynchronous additive
* Function(argument) : Observe(sum) where sum is a numeric value
* Default aggregation : Sum
* Notes : Captures only one value per Measurement Interval, part of a non-monotonic sum
*
* UpDownSumObserver is the asynchronous instrument corresponding to UpDownCounter,
* used to capture a non-monotonic count with Observe(sum).
* "Sum" appears in the name to remind users that it is used to capture sums directly.
* Use a UpDownSumObserver to capture any value that starts at zero and rises
* or falls throughout the process lifetime.
*
*/
interface UpDownSumObserver
{
/**
* Updates sum value with the positive or negative int that is passed in.
*
* @access public
* @param int|float $value
* @return int returns the non-monotonic sum
*/
public function observe($value) : int;
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Metrics;
interface ValueRecorder extends Metric
{
/**
* records the given value to this ValueRecorder.
*
* @access public
* @param int|float $value
* @return void
*/
public function record($value) : void;
/**
* Returns the sum of the values
*
* @access public
* @return float
*/
public function getSum(): float;
/**
* Returns the min of the values
*
* @access public
* @return float
*/
public function getMin(): float;
/**
* Returns the max of the values
*
* @access public
* @return float
*/
public function getMax(): float;
/**
* Returns the count of the values
*
* @access public
* @return int
*/
public function getCount(): int;
}

17
api/Trace/Attribute.php Normal file
View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
interface Attribute
{
public function getKey(): string;
/**
*
* @return bool|int|float|string|list<bool>|list<int>|list<float>|list<string>
*
*/
public function getValue();
}

19
api/Trace/Attributes.php Normal file
View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
interface Attributes extends \IteratorAggregate, \Countable
{
/**
* Setting event should not invalidate nor change any existing iterators.
* @param string $name
* @param mixed $value
* @return Attributes
*/
public function setAttribute(string $name, $value): Attributes;
public function count(): int;
public function getIterator(): AttributesIterator;
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
interface AttributesIterator extends \Iterator
{
public function key(): string;
public function current(): Attribute;
/**
* Should be valid to call rewind as many times as desired UNTIL next() has been called; then it is implementation
* defined whether it is valid or not. The implementation should throw if it cannot be rewound.
*/
public function rewind(): void;
public function valid(): bool;
public function next(): void;
}

16
api/Trace/Clock.php Normal file
View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
interface Clock
{
/**
* A combination of the Monotonic and Realtime Clocks
* Monotonic time value in the first slot, as it'll get accessed more frequently in duration calculations.
* @return array
*/
public function moment(): array;
public function now(): int;
}

12
api/Trace/Event.php Normal file
View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
interface Event
{
public function getName(): string;
public function getAttributes(): Attributes;
public function getTimestamp(): int;
}

20
api/Trace/Events.php Normal file
View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
interface Events extends \IteratorAggregate, \Countable
{
/**
* Adding an event should not invalidate nor change any existing iterators.
* @param string $name
* @param Attributes|null $attributes
* @param int|null $timestamp
* @return Events Must return $this to allow setting multiple attributes at once in a chain.
*/
public function addEvent(string $name, ?Attributes $attributes = null, ?int $timestamp = null): Events;
public function count(): int;
public function getIterator(): EventsIterator;
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
interface EventsIterator extends \Iterator
{
public function key(): int;
public function current(): Event;
/**
* Should be valid to call rewind as many times as desired UNTIL next() has been called; then it is implementation
* defined whether it is valid or not. The implementation should throw if it cannot be rewound.
*/
public function rewind(): void;
public function valid(): bool;
public function next(): void;
}

11
api/Trace/Link.php Normal file
View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
interface Link
{
public function getSpanContext(): SpanContext;
public function getAttributes(): Attributes;
}

19
api/Trace/Links.php Normal file
View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
interface Links extends \IteratorAggregate, \Countable
{
public function count(): int;
public function getIterator(): LinksIterator;
/**
* Adding a link should not invalidate nor change any existing iterators.
* @param SpanContext $context
* @param Attributes|null $attributes
* @return Links Return $this to allow setting multiple links at once in a chain.
*/
public function addLink(SpanContext $context, ?Attributes $attributes = null): Links;
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
interface LinksIterator extends \Iterator
{
/**
* Should be valid to call rewind as many times as desired UNTIL next() has been called; then it is implementation
* defined whether it is valid or not. The implementation should throw if it cannot be rewound.
*/
public function rewind(): void;
public function valid(): bool;
/**
* @return int The order the link was added.
*/
public function key(): int;
/**
* @return Link Should throw if the iterator is !valid().
*/
public function current(): Link;
public function next(): void;
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
/**
* The API Specification says:
* OpenTelemetry can operate on time values up to nanosecond (ns) precision.
* The representation of those values is language specific.
* A duration is the elapsed time between two events.
* - The minimal precision is milliseconds.
* - The maximal precision is nanoseconds.
*
* In the PHP standard library, the best suited function for measuring elapsed
* time is `hrtime`, available since PHP 7.3. In other words, callers can
* reasonably expect to have nanosecond resolution (nsec) on PHP 7.3 and newer.
*/
interface MonotonicClock
{
/**
* Represents the amount of time in nanoseconds since an unspecified point
* in the past (for example, system start-up time, or the Epoch). This point
* does not change after system start-up time.
* @return int
*/
public function now(): int;
}

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
interface PropagationGetter
{
/**
* Gets the value of a given key from a carrier.
*
* @param mixed $carrier
* @param string $key
* @return string|null
*/
public function get($carrier, string $key) : ?string;
}

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
interface PropagationSetter
{
/**
* Set the value for a given key on the associated carrier.
*
* @param mixed $carrier
* @param string $key
* @param string $value
* @return void
*/
public function set(&$carrier, string $key, string $value) : void;
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
/**
* The API specification says:
* OpenTelemetry can operate on time values up to nanosecond (ns) precision.
* The representation of those values is language specific.
* A timestamp is the time elapsed since the Unix epoch.
* - The minimal precision is milliseconds.
* - The maximal precision is nanoseconds.
*
* In the PHP standard library, the best suited function for time since the
* Unix epoch is `microtime`, which uses microseconds or usecs. This interface
* uses nanosecond resolution so it can represent nanoseconds as per
* OpenTelemetry specification, but keep this mind as most implementations will
* probably use `microtime` so don't expect nanosecond accuracy for a
* RealtimeClock.
*/
interface RealtimeClock
{
/**
* @return int Number of nanoseconds since the Unix epoch
*/
public function now(): int;
}

66
api/Trace/Span.php Normal file
View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
use Throwable;
interface Span extends SpanStatus, SpanKind
{
/**
* Attributes SHOULD preserve the order in which they're set. Setting an attribute with the same key as an existing
* attribute SHOULD overwrite the existing attribute's value.
* @param string $key
* @param bool|int|float|string|array $value Note: the array MUST be homogeneous, i.e. it MUST NOT contain values
* of different types.
* @return Span Must return $this to allow setting multiple attributes at once in a chain
*/
public function setAttribute(string $key, $value): Span;
/**
* @param string $name
* @param int $timestamp
* @param Attributes|null $attributes
* @return Span Must return $this to allow setting multiple attributes at once in a chain.
*/
public function addEvent(string $name, int $timestamp, ?Attributes $attributes = null): Span;
/**
* @param SpanContext $context
* @param Attributes|null $attributes
* @return Span Must return $this to allow setting multiple links at once in a chain.
*/
public function addLink(SpanContext $context, ?Attributes $attributes = null): Span;
/**
*
* @param Throwable $exception
* @return Span Must return $this to allow setting multiple attributes at once in a chain.
*/
public function recordException(Throwable $exception, ?Attributes $attributes = null): Span;
/**
* Calling this method is highly discouraged; the name should be set on creation and left alone.
* @param string $name
* @return Span Must return $this
*/
public function updateName(string $name): Span;
/**
* Sets the Status of the Span. If used, this will override the default Span status, which is OK.
* Only the value of the last call will be recorded, and implementations are free to ignore previous calls.
* @param string $code
* @param string|null $description
* @return Span Must return $this
*/
public function setSpanStatus(string $code, ?string $description = null): Span;
/**
* @param int|null $timestamp
* @return Span Must return $this
*/
public function end(int $timestamp = null): Span;
public function isRecording(): bool;
}

18
api/Trace/SpanContext.php Normal file
View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
interface SpanContext
{
const TRACE_FLAG_SAMPLED = 1;
public function getTraceId(): string;
public function getSpanId(): string;
public function getTraceFlags(): int;
public function getTraceState(): ?TraceState;
public function isValid(): bool;
public function isRemote(): bool;
public function isSampled(): bool;
}

24
api/Trace/SpanKind.php Normal file
View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
interface SpanKind
{
const KIND_INTERNAL = 0;
const KIND_CLIENT = 1;
const KIND_SERVER = 2;
const KIND_PRODUCER = 3;
const KIND_CONSUMER = 4;
const TYPES = [
self::KIND_INTERNAL,
self::KIND_CLIENT,
self::KIND_SERVER,
self::KIND_PRODUCER,
self::KIND_CONSUMER,
];
public function getSpanKind(): int;
}

40
api/Trace/SpanOptions.php Normal file
View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
use OpenTelemetry\Context\Context;
/**
* The SpanOptions implementation is intended to be tightly coupled with the Span and Tracer implementations.
* Hopefully the Span object can implement SpanOptions so that the toSpan() call is almost a no-op, but prevents
* the rest of the SpanOptions API from being used after toSpan() is called.
*/
interface SpanOptions
{
public function setSpanName(string $name): SpanOptions;
/** should default to INTERNAL if not called */
public function setSpanKind(int $spanKind): SpanOptions;
public function setParent(Context $parentContext): SpanOptions;
public function addAttributes(Attributes $attributes): SpanOptions;
public function addLinks(Links $links): SpanOptions;
/**
* This should only be used if the creation time has already passed; will set timestamp to current time by default
* @param int $timestamp
* @return SpanOptions
*/
public function addStartTimestamp(int $timestamp): SpanOptions;
/**
* This should only be used if the creation time has already passed; will set to current monotonic clock
* value by default
* @param int $now
* @return SpanOptions
*/
public function addStart(int $now): SpanOptions;
public function toSpan(): Span;
// todo: how do we want to optionally let people make these spans active? bool arg, setActive, or toActiveSpan?
public function toActiveSpan(): Span;
}

23
api/Trace/SpanStatus.php Normal file
View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
interface SpanStatus
{
const UNSET = 'Unset';
const OK = 'Ok';
const ERROR = 'Error';
const DESCRIPTION = [
self::UNSET => 'The default unset status.',
self::OK => 'Not an error; returned on success.',
self::ERROR => 'The operation contains an error.',
];
public function getCanonicalStatusCode(): string;
public function getStatusDescription(): string;
public function isStatusOk(): bool;
// public function setStatus(string $code = self::UNSET, string $description = null): void;
}

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
interface TextMapFormatPropagator
{
/**
* Returns list of fields that will be used by this formatter.
*
* @return array
*/
public static function fields() : array;
/**
* Encodes the given SpanContext into propagator specific format and injects
* the encoded SpanContext using Setter into it's associated carrier.
*
* @param SpanContext $context
* @param mixed $carrier
* @param PropagationSetter $setter
* @return void
*/
public static function inject(SpanContext $context, &$carrier, PropagationSetter $setter) : void;
/**
* Retrieves encoded SpanContext using Getter from the associated carrier.
*
* @param mixed $carrier
* @param PropagationGetter $getter
* @return SpanContext
*/
public static function extract($carrier, PropagationGetter $getter): SpanContext;
}

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace OpenTelemetry\API\Trace;
namespace OpenTelemetry\Trace;
/**
* TraceState parses and stores the tracestate header as an immutable list of string
@ -18,27 +18,31 @@ namespace OpenTelemetry\API\Trace;
* @see https://www.w3.org/TR/trace-context/#tracestate-header
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/api.md#tracestate
*/
interface TraceStateInterface
interface TraceState
{
/**
* Return a new TraceState object that inherits from this TraceState
* and contains the given key value pair.
*
* @return TraceStateInterface
* @param string $key
* @param string $value
* @return TraceState
*/
public function with(string $key, string $value): TraceStateInterface;
public function with(string $key, string $value): TraceState;
/**
* Return a new TraceState object that inherits from this TraceState
* without the given key value pair.
*
* @return TraceStateInterface
* @param string $key
* @return TraceState
*/
public function without(string $key): TraceStateInterface;
public function without(string $key): TraceState;
/**
* Return the value of a given key from this TraceState if it exists
*
* @param string $key
* @return string|null
*/
public function get(string $key): ?string;
@ -51,17 +55,9 @@ interface TraceStateInterface
public function getListMemberCount(): int;
/**
* Returns the concatenated string representation.
* Build the TraceState header
*
* @param int|null $limit maximum length of the returned representation
* @return string the string representation
*
* @see https://www.w3.org/TR/trace-context/#tracestate-limits
* @return string|null
*/
public function toString(?int $limit = null): string;
/**
* Returns a string representation of this TraceSate
*/
public function __toString(): string;
public function build(): ?string;
}

27
api/Trace/Tracer.php Normal file
View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
use OpenTelemetry\Context\Context;
interface Tracer
{
public function startSpan(
string $name,
?Context $parentContext = null,
int $spanKind = SpanKind::KIND_INTERNAL,
?Attributes $attributes = null,
?Links $links = null,
?int $startTimestamp = null
): Span;
public function getActiveSpan(): Span;
public function startActiveSpan(string $name, SpanContext $parentContext, bool $isRemote = false, int $spanKind = SpanKind::KIND_INTERNAL): Span;
public function startAndActivateSpan(string $name, int $spanKind = SpanKind::KIND_INTERNAL): Span;
public function startSpanWithOptions(string $name): SpanOptions;
public function setActiveSpan(Span $span): void;
}

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Trace;
interface TracerProvider
{
public function getTracer(string $name, ?string $version = null): Tracer;
}

View File

@ -1,208 +1,65 @@
{
"name": "open-telemetry/opentelemetry",
"description": "OpenTelemetry makes robust, portable telemetry a built-in feature of cloud-native software.",
"keywords": ["opentelemetry", "otel", "open-telemetry", "tracing", "logging", "metrics"],
"type": "library",
"homepage": "https://opentelemetry.io/docs/php",
"readme": "./README.md",
"license": "Apache-2.0",
"require": {
"php": "^8.1",
"google/protobuf": "^3.22 || ^4.0",
"nyholm/psr7-server": "^1.1",
"php-http/discovery": "^1.14",
"psr/http-client": "^1.0",
"php": "^7.3 || ^8.0",
"ext-json": "*",
"promphp/prometheus_client_php": "^2.2.1",
"grpc/grpc": "^1.30",
"google/protobuf": "^v3.3.0",
"psr/http-client-implementation": "^1.0",
"psr/http-factory-implementation": "^1.0",
"psr/http-message": "^1.0.1|^2.0",
"psr/log": "^1.1|^2.0|^3.0",
"ramsey/uuid": "^3.0 || ^4.0",
"symfony/config": "^5.4 || ^6.4 || ^7.0",
"symfony/polyfill-mbstring": "^1.23",
"symfony/polyfill-php82": "^1.26",
"symfony/polyfill-php83": "^1.32",
"symfony/polyfill-php84": "^1.32",
"tbachert/spi": "^1.0.1"
},
"config": {
"sort-packages": true,
"allow-plugins": {
"bamarni/composer-bin-plugin": true,
"composer/package-versions-deprecated": true,
"php-http/discovery": true,
"symfony/runtime": true,
"tbachert/spi": true
}
"nyholm/dsn": "^2.0.0"
},
"authors": [
{
"name": "opentelemetry-php contributors",
"homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors"
"name": "Bob Strecansky",
"email": "bob.strecansky@gmail.com"
},
{
"name": "Dmitry Krokhin",
"email": "nekufa@gmail.com"
},
{
"name": "Levi Morrison",
"email": "levim@php.net"
}
],
"replace": {
"open-telemetry/api": "1.0.x-dev",
"open-telemetry/context": "1.0.x-dev",
"open-telemetry/exporter-otlp": "1.0.x-dev",
"open-telemetry/exporter-zipkin": "1.0.x-dev",
"open-telemetry/extension-propagator-b3": "1.0.x-dev",
"open-telemetry/extension-propagator-cloudtrace": "1.0.x-dev",
"open-telemetry/extension-propagator-jaeger": "0.0.2",
"open-telemetry/gen-otlp-protobuf": "1.0.x-dev",
"open-telemetry/sdk": "1.0.x-dev",
"open-telemetry/sdk-configuration": "0.1.x-dev",
"open-telemetry/sdk-contrib": "1.0.x-dev",
"open-telemetry/sem-conv": "1.0.x-dev",
"open-telemetry/transport-grpc": "1.0.x-dev"
},
"autoload": {
"psr-4": {
"OpenTelemetry\\": "src/",
"Opentelemetry\\Proto\\": "proto/otel/Opentelemetry/Proto/",
"GPBMetadata\\Opentelemetry\\": "proto/otel/GPBMetadata/Opentelemetry/"
},
"files": [
"src/Context/fiber/initialize_fiber_handler.php",
"src/API/Trace/functions.php",
"src/Contrib/Otlp/_register.php",
"src/Contrib/Grpc/_register.php",
"src/Contrib/Zipkin/_register.php",
"src/Extension/Propagator/B3/_register.php",
"src/Extension/Propagator/CloudTrace/_register.php",
"src/Extension/Propagator/Jaeger/_register.php",
"src/SDK/Logs/Exporter/_register.php",
"src/SDK/Metrics/MetricExporter/_register.php",
"src/SDK/Propagation/_register.php",
"src/SDK/Trace/SpanExporter/_register.php",
"src/SDK/Common/Dev/Compatibility/_load.php",
"src/SDK/Common/Util/functions.php",
"src/SDK/_autoload.php"
]
"OpenTelemetry\\Context\\": "Context",
"OpenTelemetry\\": "api",
"OpenTelemetry\\Sdk\\": "sdk",
"OpenTelemetry\\Contrib\\": "contrib",
"Opentelemetry\\Proto\\Collector\\Trace\\V1\\": "proto/Opentelemetry/Proto/Collector/Trace/V1",
"Opentelemetry\\Proto\\Trace\\V1\\":"proto/Opentelemetry/Proto/Trace/V1",
"Opentelemetry\\Proto\\Common\\V1\\":"proto/Opentelemetry/Proto/Common/V1",
"Opentelemetry\\Proto\\Resource\\V1\\":"proto/Opentelemetry/Proto/Resource/V1",
"GPBMetadata\\Opentelemetry\\Proto\\Collector\\Trace\\V1\\": "proto/GPBMetadata/Opentelemetry/Proto/Collector/Trace/V1",
"GPBMetadata\\Opentelemetry\\Proto\\Trace\\V1\\":"proto/GPBMetadata/Opentelemetry/Proto/Trace/V1",
"GPBMetadata\\Opentelemetry\\Proto\\Common\\V1\\":"proto/GPBMetadata/Opentelemetry/Proto/Common/V1",
"GPBMetadata\\Opentelemetry\\Proto\\Resource\\V1\\":"proto/GPBMetadata/Opentelemetry/Proto/Resource/V1"
}
},
"autoload-dev": {
"psr-4": {
"OpenTelemetry\\Tests\\": "tests/",
"OpenTelemetry\\Example\\": "examples/src/",
"ExampleSDK\\ComponentProvider\\": "tests/Unit/Config/SDK/Configuration/ExampleSdk/"
"OpenTelemetry\\Tests\\": "tests/"
}
},
"require-dev": {
"ext-grpc": "*",
"bamarni/composer-bin-plugin": "^1.8",
"dg/bypass-finals": "^1.4",
"grpc/grpc": "^1.30",
"guzzlehttp/guzzle": "^7.4",
"guzzlehttp/psr7": "^2.1",
"mikey179/vfsstream": "^1.6.11",
"mockery/mockery": "^1.5.1",
"monolog/monolog": "^3.0",
"nyholm/psr7": "^1.4",
"open-telemetry/dev-tools": "dev-main",
"php-http/mock-client": "^1.5",
"phpdocumentor/reflection-docblock": "^5.3",
"phpspec/prophecy": "^1.22",
"phpspec/prophecy-phpunit": "^2",
"phpstan/phpstan": "^1.10.13",
"phpstan/phpstan-mockery": "^1.1",
"phpstan/phpstan-phpunit": "^1.3",
"phpunit/phpunit": "^10 || ^11",
"sebastian/exporter": "<= 6.0.1 || >= 6.1.3",
"phpunit/phpunit": "^9.3",
"composer/xdebug-handler": "^2.0",
"phan/phan": "^4.1",
"friendsofphp/php-cs-fixer": "^3.0",
"vimeo/psalm": "^4.0",
"phpstan/phpstan": "^0.12.50",
"phpstan/phpstan-phpunit": "^0.12.16",
"psalm/plugin-phpunit": "^0.13.0",
"guzzlehttp/guzzle": "^7.3",
"guzzlehttp/psr7": "^2.0@RC",
"symfony/http-client": "^5.2",
"symfony/var-exporter": "^5.4 || ^6.4 || ^7.0",
"symfony/yaml": "^5.4 || ^6.4 || ^7.0"
},
"conflict": {
"mockery/mockery": "1.6.12"
},
"suggest": {
"ext-gmp": "To support unlimited number of synchronous metric readers",
"ext-grpc": "To use the OTLP GRPC Exporter",
"ext-protobuf": "For more performant protobuf/grpc exporting",
"ext-yaml": "Allows loading config from yaml files",
"symfony/yaml": "Allows loading config from yaml files"
},
"extra": {
"bamarni-bin": {
"bin-links": false,
"target-directory": "vendor-bin",
"forward-command": true
},
"spi": {
"OpenTelemetry\\API\\Configuration\\Config\\ComponentProvider": [
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Propagator\\TextMapPropagatorB3",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Propagator\\TextMapPropagatorB3Multi",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Propagator\\TextMapPropagatorBaggage",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Propagator\\TextMapPropagatorComposite",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Propagator\\TextMapPropagatorJaeger",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Propagator\\TextMapPropagatorTraceContext",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SamplerAlwaysOff",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SamplerAlwaysOn",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SamplerParentBased",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SamplerTraceIdRatioBased",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SpanExporterConsole",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SpanExporterMemory",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SpanExporterOtlpFile",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SpanExporterOtlpGrpc",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SpanExporterOtlpHttp",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SpanExporterZipkin",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SpanProcessorBatch",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SpanProcessorSimple",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Metrics\\AggregationResolverDefault",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Metrics\\MetricExporterConsole",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Metrics\\MetricExporterMemory",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Metrics\\MetricExporterOtlpFile",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Metrics\\MetricExporterOtlpGrpc",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Metrics\\MetricExporterOtlpHttp",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Metrics\\MetricReaderPeriodic",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Logs\\LogRecordExporterConsole",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Logs\\LogRecordExporterMemory",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Logs\\LogRecordExporterOtlpFile",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Logs\\LogRecordExporterOtlpGrpc",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Logs\\LogRecordExporterOtlpHttp",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Logs\\LogRecordProcessorBatch",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Logs\\LogRecordProcessorSimple",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Detector\\Composer",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Detector\\Host",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Detector\\Process",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Instrumentation\\General\\HttpConfigProvider",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Instrumentation\\General\\PeerConfigProvider",
"OpenTelemetry\\Example\\ExampleConfigProvider",
"OpenTelemetry\\Tests\\Integration\\Config\\ComponentProvider\\Detector\\Container",
"OpenTelemetry\\Tests\\Integration\\Config\\ComponentProvider\\Detector\\Os",
"OpenTelemetry\\Tests\\Integration\\Config\\ComponentProvider\\Metrics\\AggregationResolverExplicitBucketHistogram",
"OpenTelemetry\\Tests\\Integration\\Config\\ComponentProvider\\Metrics\\MetricExporterPrometheus",
"OpenTelemetry\\Tests\\Integration\\Config\\ComponentProvider\\Metrics\\MetricReaderPull",
"OpenTelemetry\\Tests\\Integration\\Config\\ComponentProvider\\Propagator\\TextMapPropagatorXray",
"OpenTelemetry\\Tests\\Integration\\Config\\ComponentProvider\\Propagator\\TextMapPropagatorOtTrace"
],
"OpenTelemetry\\API\\Configuration\\ConfigEnv\\EnvComponentLoader": [
"OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderHttpConfig",
"OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderPeerConfig",
"OpenTelemetry\\Example\\ExampleConfigLoader"
],
"OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [
"OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager"
],
"OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\Instrumentation": [
"OpenTelemetry\\Example\\ExampleInstrumentation"
],
"OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\ResolverInterface": [
"OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\SdkConfigurationResolver"
],
"OpenTelemetry\\Config\\SDK\\Configuration\\Environment\\EnvSourceProvider": [
"OpenTelemetry\\Config\\SDK\\Configuration\\Environment\\Adapter\\SymfonyDotenvProvider",
"OpenTelemetry\\Config\\SDK\\Configuration\\Environment\\Adapter\\VlucasPhpdotenvProvider",
"OpenTelemetry\\Tests\\Integration\\SDK\\Common\\Configuration\\TestEnvSourceProvider"
]
}
"nyholm/psr7": "^1.4"
}
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Contrib\Jaeger;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use OpenTelemetry\Contrib\Zipkin;
use OpenTelemetry\Sdk\Trace;
class Exporter extends Zipkin\Exporter implements Trace\Exporter
{
public static function fromConnectionString(string $endpointUrl, string $name, $args = null)
{
$factory = new HttpFactory();
$exporter = new Exporter(
$name,
$endpointUrl,
new Client(),
$factory,
$factory
);
return $exporter;
}
}

View File

@ -0,0 +1,182 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Contrib\Newrelic;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use InvalidArgumentException;
use OpenTelemetry\Sdk\Trace;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Client\RequestExceptionInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
/**
* Class NewrelicExporter - implements the export interface for data transfer via Newrelic protocol
* @package OpenTelemetry\Exporter
*
* This is an experimental, non-supported exporter.
* It will send PHP Otel trace data end to end across the internet to a functional backend.
* Needs a license key to connect. For a free account/key, go to: https://newrelic.com/signup/
*/
class Exporter implements Trace\Exporter
{
private const DATA_FORMAT = 'newrelic';
private const DATA_FORMAT_VERSION_DEFAULT = '1';
/**
* @var string
*/
private $endpointUrl;
/**
* @var string
*/
private $licenseKey;
/**
* @var string
*/
private $name;
/**
* @var SpanConverter
*/
private $spanConverter;
/**
* @var bool
*/
private $running = true;
/**
* @var ClientInterface
*/
private $client;
/**
* @var RequestFactoryInterface
*/
private $requestFactory;
/**
* @var StreamFactoryInterface
*/
private $streamFactory;
private $dataFormatVersion;
public function __construct(
$name,
string $endpointUrl,
string $licenseKey,
ClientInterface $client,
RequestFactoryInterface $requestFactory,
StreamFactoryInterface $streamFactory,
SpanConverter $spanConverter = null,
string $dataFormatVersion = Exporter::DATA_FORMAT_VERSION_DEFAULT
) {
$parsedDsn = parse_url($endpointUrl);
if (!is_array($parsedDsn)) {
throw new InvalidArgumentException('Unable to parse provided DSN');
}
if (
!isset($parsedDsn['scheme'])
|| !isset($parsedDsn['host'])
|| !isset($parsedDsn['path'])
) {
throw new InvalidArgumentException('Endpoint should have scheme, host, port and path');
}
$this->name = $name;
$this->endpointUrl = $endpointUrl;
$this->licenseKey = $licenseKey;
$this->client = $client;
$this->requestFactory = $requestFactory;
$this->streamFactory = $streamFactory;
$this->spanConverter = $spanConverter ?? new SpanConverter($name);
$this->dataFormatVersion = $dataFormatVersion;
}
/**
* Exports the provided Span data via the Newrelic protocol
*
* @param iterable<Trace\ReadableSpan> $spans Array of Spans
* @return int return code, defined on the Exporter interface
*/
public function export(iterable $spans): int
{
if (!$this->running) {
return Exporter::FAILED_NOT_RETRYABLE;
}
if (empty($spans)) {
return Trace\Exporter::SUCCESS;
}
$convertedSpans = [];
foreach ($spans as $span) {
array_push($convertedSpans, $this->spanConverter->convert($span));
}
$commonAttributes = ['attributes' => [ 'service.name' => $this->name,
'host' => $this->endpointUrl, ]];
$payload = [[ 'common' => $commonAttributes,
'spans' => $convertedSpans, ]];
try {
$body = $this->streamFactory->createStream(json_encode($payload));
$request = $this->requestFactory
->createRequest('POST', $this->endpointUrl)
->withBody($body)
->withHeader('content-type', 'application/json')
->withAddedHeader('Api-Key', $this->licenseKey)
->withAddedHeader('Data-Format', Exporter::DATA_FORMAT)
->withAddedHeader('Data-Format-Version', $this->dataFormatVersion);
$response = $this->client->sendRequest($request);
} catch (RequestExceptionInterface $e) {
return Trace\Exporter::FAILED_NOT_RETRYABLE;
} catch (NetworkExceptionInterface | ClientExceptionInterface $e) {
return Trace\Exporter::FAILED_RETRYABLE;
}
if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 500) {
return Trace\Exporter::FAILED_NOT_RETRYABLE;
}
if ($response->getStatusCode() >= 500 && $response->getStatusCode() < 600) {
return Trace\Exporter::FAILED_RETRYABLE;
}
return Trace\Exporter::SUCCESS;
}
public function shutdown(): void
{
$this->running = false;
}
public static function fromConnectionString(string $endpointUrl, string $name, $args)
{
if ($args == false) {
throw new Exception('Invalid license key.');
}
$factory = new HttpFactory();
$exporter = new Exporter(
$name,
$endpointUrl,
$args,
new Client(),
$factory,
$factory
);
return $exporter;
}
}

View File

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Contrib\Newrelic;
use OpenTelemetry\Sdk\Trace\ReadableSpan;
class SpanConverter
{
const STATUS_CODE_TAG_KEY = 'otel.status_code';
const STATUS_DESCRIPTION_TAG_KEY = 'otel.status_description';
/**
* @var string
*/
private $serviceName;
public function __construct(string $serviceName)
{
$this->serviceName = $serviceName;
}
public function convert(ReadableSpan $span)
{
$spanParent = $span->getParent();
$row = [
'id' => $span->getContext()->getSpanId(),
'trace.id' => $span->getContext()->getTraceId(),
'attributes' => [
'name' => $span->getSpanName(),
'service.name' => $this->serviceName,
'parent.id' => $spanParent ? $spanParent->getSpanId() : null,
'timestamp' => ($span->getStartEpochTimestamp() / 1e6), // RealtimeClock in milliseconds
'duration.ms' => (($span->getEnd() - $span->getStart()) / 1e6), // Diff in milliseconds
self::STATUS_CODE_TAG_KEY => $span->getStatus()->getCanonicalStatusCode(),
self::STATUS_DESCRIPTION_TAG_KEY => $span->getStatus()->getStatusDescription(),
],
];
foreach ($span->getAttributes() as $k => $v) {
$row['attributes'][$k] = $v->getValue();
}
/*
foreach ($span->getEvents() as $event) {
if (!array_key_exists('annotations', $row)) {
$row['annotations'] = [];
}
$row['annotations'][] = [
'timestamp' => (int) ($event->getTimestamp() / 1e6), // RealtimeClock in milliseconds
'value' => $event->getName(),
];
}
*/
return $row;
}
}

181
contrib/Otlp/Exporter.php Normal file
View File

@ -0,0 +1,181 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Contrib\Otlp;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use OpenTelemetry\Contrib\OtlpGrpc\Exporter as OtlpGrpcExporter;
use OpenTelemetry\Contrib\OtlpHttp\Exporter as OtlpHttpExporter;
use OpenTelemetry\Sdk\Trace;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Client\RequestExceptionInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
class Exporter implements Trace\Exporter
{
/**
* @var string
*/
private $endpointUrl;
/**
* @var string
*/
private $protocol;
/**
* @var string
*/
private $insecure;
/**
* @var string
*/
private $certificateFile;
/**
* @var array
*/
private $headers;
/**
* @var string
*/
private $compression;
/**
* @var int
*/
private $timeout;
/**
* @var SpanConverter
*/
private $spanConverter;
/**
* @var bool
*/
private $running = true;
/**
* @var ClientInterface
*/
private $client;
/**
* @var RequestFactoryInterface
*/
private $requestFactory;
/**
* @var StreamFactoryInterface
*/
private $streamFactory;
/**
* Exporter constructor.
* @param string $serviceName
*/
public function __construct(
$serviceName,
ClientInterface $client,
RequestFactoryInterface $requestFactory,
StreamFactoryInterface $streamFactory,
SpanConverter $spanConverter = null
) {
// Set default values based on presence of env variable
$this->endpointUrl = getenv('OTEL_EXPORTER_OTLP_ENDPOINT') ?: 'localhost:55681';
$this->protocol = getenv('OTEL_EXPORTER_OTLP_PROTOCOL') ?: 'json';
$this->insecure = getenv('OTEL_EXPORTER_OTLP_INSECURE') ?: 'false';
$this->certificateFile = getenv('OTEL_EXPORTER_OTLP_CERTIFICATE') ?: 'none';
$this->headers[] = getenv('OTEL_EXPORTER_OTLP_HEADERS') ?: 'none';
$this->compression = getenv('OTEL_EXPORTER_OTLP_COMPRESSION') ?: 'none';
$this->timeout =(int) getenv('OTEL_EXPORTER_OTLP_TIMEOUT') ?: 10;
$this->client = $client;
$this->requestFactory = $requestFactory;
$this->streamFactory = $streamFactory;
$this->spanConverter = $spanConverter ?? new SpanConverter($serviceName);
}
/**
* Exports the provided Span data via the OTLP protocol
*
* @param iterable<Trace\ReadableSpan> $spans Array of Spans
* @return int return code, defined on the Exporter interface
*/
public function export(iterable $spans): int
{
if (!$this->running) {
return Exporter::FAILED_NOT_RETRYABLE;
}
if (empty($spans)) {
return Trace\Exporter::SUCCESS;
}
$convertedSpans = [];
foreach ($spans as $span) {
array_push($convertedSpans, $this->spanConverter->convert($span));
}
try {
$body = $this->streamFactory->createStream(json_encode($convertedSpans));
$request = $this->requestFactory
->createRequest('POST', $this->endpointUrl)
->withBody($body);
if ($this->protocol == 'json') {
$request->withHeader('content-type', 'application/json');
}
$response = $this->client->sendRequest($request);
} catch (RequestExceptionInterface $e) {
return Trace\Exporter::FAILED_NOT_RETRYABLE;
} catch (NetworkExceptionInterface | ClientExceptionInterface $e) {
return Trace\Exporter::FAILED_RETRYABLE;
}
if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 500) {
return Trace\Exporter::FAILED_NOT_RETRYABLE;
}
if ($response->getStatusCode() >= 500 && $response->getStatusCode() < 600) {
return Trace\Exporter::FAILED_RETRYABLE;
}
return Trace\Exporter::SUCCESS;
}
public function shutdown(): void
{
$this->running = false;
}
public static function fromConnectionString(string $endpointUrl, string $name, $args)
{
// @phan-suppress-next-line PhanUndeclaredFunction
if ($args !== '' && str_contains($args, 'grpc')) {
return OtlpGrpcExporter::fromConnectionString();
}
// @phan-suppress-next-line PhanUndeclaredFunction
if ($args !== '' && str_contains($args, 'http')) {
return OtlpHttpExporter::fromConnectionString();
}
$factory = new HttpFactory();
$exporter = new Exporter(
$name,
new Client(),
$factory,
$factory
);
return $exporter;
}
}

View File

@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Contrib\Otlp;
use OpenTelemetry\Sdk\Trace\ReadableSpan;
class SpanConverter
{
/**
* @var string
*/
private $serviceName;
public function __construct(string $serviceName)
{
$this->serviceName = $serviceName;
}
private function sanitiseTagValue($value)
{
// Casting false to string makes an empty string
if (is_bool($value)) {
return $value ? 'true' : 'false';
}
// OTLP tags must be strings, but opentelemetry
// accepts strings, booleans, numbers, and lists of each.
if (is_array($value)) {
return join(',', array_map([$this, 'sanitiseTagValue'], $value));
}
// Floats will lose precision if their string representation
// is >=14 or >=17 digits, depending on PHP settings.
// Can also throw E_RECOVERABLE_ERROR if $value is an object
// without a __toString() method.
// This is possible because OpenTelemetry\Trace\Span does not verify
// setAttribute() $value input.
return (string) $value;
}
public function convert(ReadableSpan $span)
{
$spanParent = $span->getParent();
$row = [
'id' => $span->getContext()->getSpanId(),
'traceId' => $span->getContext()->getTraceId(),
'parentId' => $spanParent ? $spanParent->getSpanId() : null,
'localEndpoint' => [
'serviceName' => $this->serviceName,
],
'name' => $span->getSpanName(),
'timestamp' => (int) ($span->getStartEpochTimestamp() / 1e3), // RealtimeClock in microseconds
'duration' => (int) (($span->getEnd() - $span->getStart()) / 1e3), // Diff in microseconds
];
foreach ($span->getAttributes() as $k => $v) {
if (!array_key_exists('tags', $row)) {
$row['tags'] = [];
}
$row['tags'][$k] = $this->sanitiseTagValue($v->getValue());
}
foreach ($span->getEvents() as $event) {
if (!array_key_exists('annotations', $row)) {
$row['annotations'] = [];
}
$row['annotations'][] = [
'timestamp' => (int) ($event->getTimestamp() / 1e3), // RealtimeClock in microseconds
'value' => $event->getName(),
];
}
return $row;
}
}

View File

@ -0,0 +1,217 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Contrib\OtlpGrpc;
use grpc;
use InvalidArgumentException;
use Opentelemetry\Proto\Collector\Trace\V1\ExportTraceServiceRequest;
use Opentelemetry\Proto\Collector\Trace\V1\TraceServiceClient;
use OpenTelemetry\Sdk\Trace;
class Exporter implements Trace\Exporter
{
/**
* @var string
*/
private $endpointURL;
/**
* @var string
*/
private $protocol;
/**
* @var bool|string
*/
private $insecure;
/**
* @var string
*/
private $certificateFile;
/**
* @var string
*/
private $headers;
/**
* @var bool|string
*/
private $compression;
/**
* @var int
*/
private $timeout;
/**
* @var SpanConverter
*/
private $spanConverter;
private $metadata;
/**
* @var bool
*/
private $running = true;
/**
* @var TraceServiceClient
*/
private $client;
/**
* OTLP GRPC Exporter Constructor
*/
public function __construct(
string $endpointURL = 'localhost:4317',
bool $insecure = true,
string $certificateFile = '',
string $headers = '',
bool $compression = false,
int $timeout = 10,
TraceServiceClient $client = null
) {
// Set default values based on presence of env variable
$this->endpointURL = getenv('OTEL_EXPORTER_OTLP_ENDPOINT') ?: $endpointURL;
$this->protocol = getenv('OTEL_EXPORTER_OTLP_PROTOCOL') ?: 'grpc'; // I guess this is redundant?
$this->insecure = getenv('OTEL_EXPORTER_OTLP_INSECURE') ? filter_var(getenv('OTEL_EXPORTER_OTLP_INSECURE'), FILTER_VALIDATE_BOOLEAN): $insecure;
$this->certificateFile = getenv('OTEL_EXPORTER_OTLP_CERTIFICATE') ?: $certificateFile;
$this->headers = getenv('OTEL_EXPORTER_OTLP_HEADERS') ?: $headers;
$this->compression = getenv('OTEL_EXPORTER_OTLP_COMPRESSION') ?: $compression;
$this->timeout =(int) getenv('OTEL_EXPORTER_OTLP_TIMEOUT') ?: $timeout;
$this->spanConverter = new SpanConverter();
$this->metadata = $this->metadataFromHeaders($this->headers);
$opts = $this->getClientOptions();
$this->client = $client ?? new TraceServiceClient($this->endpointURL, $opts);
}
public function getClientOptions(): array
{
$opts = [
'update_metadata' => function () {
return $this->metadata;
},
'timeout' => $this->timeout,
];
if (!$this->insecure && !$this->certificateFile) {
// Assumed default
$opts['credentials'] = Grpc\ChannelCredentials::createSsl('');
} elseif (!$this->insecure && $this->certificateFile !== '') {
// Should we validate more?
$opts['credentials'] = Grpc\ChannelCredentials::createSsl(file_get_contents($this->certificateFile));
} else {
$opts['credentials'] = Grpc\ChannelCredentials::createInsecure();
}
if ($this->compression) {
// gzip is the only specified compression method for now
$opts['grpc.default_compression_algorithm'] = 2;
}
return $opts;
}
/**
* Exports the provided Span data via the OTLP protocol
*
* @param iterable<Trace\ReadableSpan> $spans Array of Spans
* @return int return code, defined on the Exporter interface
*/
public function export(iterable $spans): int
{
if (!$this->running) {
return Exporter::FAILED_NOT_RETRYABLE;
}
if (empty($spans)) {
return Trace\Exporter::SUCCESS;
}
$resourcespans = [$this->spanConverter->as_otlp_resource_span($spans)];
$request = new ExportTraceServiceRequest([
'resource_spans' => $resourcespans,
]);
list($response, $status) = $this->client->Export($request)->wait();
if ($status->code == \Grpc\STATUS_OK) {
return Trace\Exporter::SUCCESS;
}
if (in_array($status->code, [
\Grpc\STATUS_CANCELLED,
\Grpc\STATUS_DEADLINE_EXCEEDED,
\Grpc\STATUS_PERMISSION_DENIED,
\Grpc\STATUS_RESOURCE_EXHAUSTED,
\Grpc\STATUS_ABORTED,
\Grpc\STATUS_OUT_OF_RANGE,
\Grpc\STATUS_UNAVAILABLE,
\Grpc\STATUS_DATA_LOSS,
\Grpc\STATUS_UNAUTHENTICATED,
])) {
return Trace\Exporter::FAILED_RETRYABLE;
}
return Trace\Exporter::FAILED_NOT_RETRYABLE;
}
public function setHeader($key, $value)
{
$this->metadata[$key] = [$value];
}
public function getHeaders()
{
return $this->metadata;
}
public function metadataFromHeaders($headers): array
{
if (is_array($headers)) {
throw new InvalidArgumentException('Configuring Headers Via');
}
if (strlen($headers) <= 0) {
return [];
}
$pairs = explode(',', $headers);
$metadata = [];
foreach ($pairs as $pair) {
if (!strpos($pair, '=')) {
continue;
}
list($key, $value) = explode('=', $pair, 2);
$metadata[$key] = [$value];
}
return $metadata;
}
public function shutdown(): void
{
$this->running = false;
}
public static function fromConnectionString(string $endpointUrl = null, string $name = null, $args = null)
{
return new Exporter();
}
// public function getHeaders()
// {
// return $this->metadataFromHeaders($this->headers);
// }
}

View File

@ -0,0 +1,189 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Contrib\OtlpGrpc;
use Opentelemetry\Proto;
use Opentelemetry\Proto\Common\V1\AnyValue;
use Opentelemetry\Proto\Common\V1\ArrayValue;
use Opentelemetry\Proto\Common\V1\InstrumentationLibrary;
use Opentelemetry\Proto\Common\V1\KeyValue;
use Opentelemetry\Proto\Trace\V1\InstrumentationLibrarySpans;
use Opentelemetry\Proto\Trace\V1\ResourceSpans;
use Opentelemetry\Proto\Trace\V1\Span as CollectorSpan;
use Opentelemetry\Proto\Trace\V1\Span\Event;
use Opentelemetry\Proto\Trace\V1\Span\SpanKind;
use Opentelemetry\Proto\Trace\V1\Status;
use Opentelemetry\Proto\Trace\V1\Status\StatusCode;
use OpenTelemetry\Sdk\Trace\ReadableSpan;
use OpenTelemetry\Sdk\Trace\SpanStatus;
class SpanConverter
{
public function as_otlp_key_value($key, $value): KeyValue
{
return new KeyValue([
'key' => $key,
'value' => $this->as_otlp_any_value($value),
]);
}
public function as_otlp_any_value($value): AnyValue
{
$result = new AnyValue();
switch (true) {
case is_array($value):
$values = [];
foreach ($value as $element) {
$this->as_otlp_any_value($element);
}
$result->setArrayValue(new ArrayValue($values));
break;
case is_int($value):
$result->setIntValue($value);
break;
case is_bool($value):
$result->setBoolValue($value);
break;
case is_double($value):
$result->setDoubleValue($value);
break;
case is_string($value):
$result->setStringValue($value);
break;
}
return $result;
}
public function as_otlp_span_kind($kind): int
{
switch ($kind) {
case 0: return SpanKind::SPAN_KIND_INTERNAL;
case 1: return SpanKind::SPAN_KIND_CLIENT;
case 2: return SpanKind::SPAN_KIND_SERVER;
case 3: return SpanKind::SPAN_KIND_PRODUCER;
case 4: return SpanKind::SPAN_KIND_CONSUMER;
}
return SpanKind::SPAN_KIND_UNSPECIFIED;
}
public function as_otlp_span(ReadableSpan $span): CollectorSpan
{
$end_timestamp = ($span->getStartEpochTimestamp() + $span->getDuration());
$parent_span = $span->getParent();
$parent_span_id = $parent_span ? $parent_span->getSpanId() : false;
$row = [
'trace_id' => hex2bin($span->getContext()->getTraceId()),
'span_id' => hex2bin($span->getContext()->getSpanId()),
'parent_span_id' => $parent_span_id ? hex2bin($parent_span_id) : null,
'name' => $span->getSpanName(),
'start_time_unix_nano' => $span->getStartEpochTimestamp(),
'end_time_unix_nano' => $end_timestamp,
'kind' => $this->as_otlp_span_kind($span->getSpanKind()),
// 'trace_state' => $span->getContext()
// 'links' =>
];
foreach ($span->getEvents() as $event) {
if (!array_key_exists('events', $row)) {
$row['events'] = [];
}
$attrs = [];
foreach ($event->getAttributes() as $k => $v) {
array_push($attrs, $this->as_otlp_key_value($k, $v->getValue()));
}
$row['events'][] = new Event([
'time_unix_nano' => $event->getTimestamp(),
'name' => $event->getName(),
'attributes' => $attrs,
]);
}
foreach ($span->getAttributes() as $k => $v) {
if (!array_key_exists('attributes', $row)) {
$row['attributes'] = [];
}
array_push($row['attributes'], $this->as_otlp_key_value($k, $v->getValue()));
}
$status = new Status();
switch ($span->getStatus()->getCanonicalStatusCode()) {
case SpanStatus::OK:
$status->setCode(StatusCode::STATUS_CODE_OK);
break;
case SpanStatus::ERROR:
$status->setCode(StatusCode::STATUS_CODE_ERROR)->setMessage($span->getStatus()->getStatusDescription());
break;
default:
$status->setCode(StatusCode::STATUS_CODE_UNSET);
}
$row['status'] = $status;
return new CollectorSpan($row);
}
// @return KeyValue[]
public function as_otlp_resource_attributes(iterable $spans): array
{
$attrs = [];
foreach ($spans as $span) {
foreach ($span->getResource()->getAttributes() as $k => $v) {
array_push($attrs, $this->as_otlp_key_value($k, $v->getValue()));
}
}
return $attrs;
}
public function as_otlp_resource_span(iterable $spans): ResourceSpans
{
// TODO: Should return an empty ResourceSpans when $spans is empty
// At the minute it returns an semi populated ResourceSpan
$ils = $convertedSpans = [];
foreach ($spans as $span) {
/** @var \OpenTelemetry\Sdk\InstrumentationLibrary $il */
$il = $span->getInstrumentationLibrary();
$ilKey = sprintf('%s@%s', $il->getName(), $il->getVersion()??'');
if (!isset($ils[$ilKey])) {
$convertedSpans[$ilKey] = [];
$ils[$ilKey] = new InstrumentationLibrary(['name' => $il->getName(), 'version' => $il->getVersion()??'']);
}
$convertedSpans[$ilKey][] = $this->as_otlp_span($span);
}
$ilSpans = [];
foreach ($ils as $ilKey => $il) {
$ilSpans[] = new InstrumentationLibrarySpans([
'instrumentation_library' => $il,
'spans' => $convertedSpans[$ilKey],
]);
}
return new Proto\Trace\V1\ResourceSpans([
'resource' => new Proto\Resource\V1\Resource([
'attributes' => $this->as_otlp_resource_attributes($spans),
]),
'instrumentation_library_spans' => $ilSpans,
]);
}
}

View File

@ -0,0 +1,227 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Contrib\OtlpHttp;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use InvalidArgumentException;
use Opentelemetry\Proto\Collector\Trace\V1\ExportTraceServiceRequest;
use OpenTelemetry\Sdk\Trace;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Client\RequestExceptionInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
class Exporter implements Trace\Exporter
{
/**
* @var string
*/
private $endpointUrl;
/**
* @var string
*/
private $protocol;
/**
* @var string
*/
private $insecure;
/**
* @var string
*/
private $certificateFile;
/**
* @var array
*/
private $headers;
/**
* @var string
*/
private $compression;
/**
* @var int
*/
private $timeout;
/**
* @var SpanConverter
*/
private $spanConverter;
/**
* @var bool
*/
private $running = true;
/**
* @var ClientInterface
*/
private $client;
/**
* @var RequestFactoryInterface
*/
private $requestFactory;
/**
* @var StreamFactoryInterface
*/
private $streamFactory;
/**
* Exporter constructor.
*/
public function __construct(
ClientInterface $client,
RequestFactoryInterface $requestFactory,
StreamFactoryInterface $streamFactory,
SpanConverter $spanConverter = null
) {
// Set default values based on presence of env variable
$this->endpointUrl = getenv('OTEL_EXPORTER_OTLP_ENDPOINT') ?: 'https://localhost:55681/v1/traces';
$this->protocol = getenv('OTEL_EXPORTER_OTLP_PROTOCOL') ?: 'http/protobuf';
$this->certificateFile = getenv('OTEL_EXPORTER_OTLP_CERTIFICATE') ?: 'none';
$this->headers = $this->processHeaders(getenv('OTEL_EXPORTER_OTLP_HEADERS'));
$this->compression = getenv('OTEL_EXPORTER_OTLP_COMPRESSION') ?: 'none';
$this->timeout =(int) getenv('OTEL_EXPORTER_OTLP_TIMEOUT') ?: 10;
$this->client = $client;
$this->requestFactory = $requestFactory;
$this->streamFactory = $streamFactory;
$this->spanConverter = $spanConverter ?? new SpanConverter();
if ($this->protocol != 'http/protobuf') {
throw new InvalidArgumentException('Invalid OTLP Protocol Specified');
}
$parsedDsn = parse_url($this->endpointUrl);
if (!is_array($parsedDsn)) {
throw new InvalidArgumentException('Unable to parse provided DSN');
}
if (
!isset($parsedDsn['scheme'])
|| !isset($parsedDsn['host'])
|| !isset($parsedDsn['port'])
|| !isset($parsedDsn['path'])
) {
throw new InvalidArgumentException('Endpoint should have scheme, host, port and path');
}
}
/**
* Exports the provided Span data via the OTLP protocol
*
* @param iterable<Trace\ReadableSpan> $spans Array of Spans
* @return int return code, defined on the Exporter interface
*/
public function export(iterable $spans): int
{
if (!$this->running) {
return Exporter::FAILED_NOT_RETRYABLE;
}
if (empty($spans)) {
return Trace\Exporter::SUCCESS;
}
$resourcespans = [$this->spanConverter->as_otlp_resource_span($spans)];
$exportrequest = new ExportTraceServiceRequest([
'resource_spans' => $resourcespans,
]);
$bytes = $exportrequest->serializeToString();
try {
$request = $this->requestFactory
->createRequest('POST', $this->endpointUrl)
->withHeader('content-type', 'application/x-protobuf');
foreach ($this->headers as $header => $value) {
$request = $request->withHeader($header, $value);
}
if ($this->compression === 'gzip') {
// TODO: Add Tests
$body = $this->streamFactory->createStream(gzencode($bytes));
$request = $request->withHeader('Content-Encoding', 'gzip');
} else {
$body = $this->streamFactory->createStream($bytes);
}
$request = $request->withBody($body);
$response = $this->client->sendRequest($request);
} catch (RequestExceptionInterface $e) {
return Trace\Exporter::FAILED_NOT_RETRYABLE;
} catch (NetworkExceptionInterface | ClientExceptionInterface $e) {
return Trace\Exporter::FAILED_RETRYABLE;
}
if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 500) {
return Trace\Exporter::FAILED_NOT_RETRYABLE;
}
if ($response->getStatusCode() >= 500 && $response->getStatusCode() < 600) {
return Trace\Exporter::FAILED_RETRYABLE;
}
return Trace\Exporter::SUCCESS;
}
/**
* processHeaders converts comma separated headers into an array
*/
public function processHeaders($headers): array
{
if (empty($headers)) {
return [];
}
$pairs = explode(',', $headers);
$metadata = [];
foreach ($pairs as $pair) {
$kv = explode('=', $pair, 2);
if (count($kv) !== 2) {
throw new InvalidArgumentException('Invalid headers passed');
}
list($key, $value) = $kv;
$metadata[$key] = $value;
}
return $metadata;
}
public function shutdown(): void
{
$this->running = false;
}
public static function fromConnectionString(string $endpointUrl = null, string $name = null, $args = null)
{
$factory = new HttpFactory();
$exporter = new Exporter(
new Client(),
$factory,
$factory
);
return $exporter;
}
}

View File

@ -0,0 +1,187 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Contrib\OtlpHttp;
use Opentelemetry\Proto;
use Opentelemetry\Proto\Common\V1\AnyValue;
use Opentelemetry\Proto\Common\V1\ArrayValue;
use Opentelemetry\Proto\Common\V1\InstrumentationLibrary;
use Opentelemetry\Proto\Common\V1\KeyValue;
use Opentelemetry\Proto\Trace\V1\InstrumentationLibrarySpans;
use Opentelemetry\Proto\Trace\V1\ResourceSpans;
use Opentelemetry\Proto\Trace\V1\Span as CollectorSpan;
use Opentelemetry\Proto\Trace\V1\Span\Event;
use Opentelemetry\Proto\Trace\V1\Span\SpanKind;
use Opentelemetry\Proto\Trace\V1\Status;
use Opentelemetry\Proto\Trace\V1\Status\StatusCode;
use OpenTelemetry\Sdk\Trace\ReadableSpan;
use OpenTelemetry\Sdk\Trace\SpanStatus;
class SpanConverter
{
public function as_otlp_key_value($key, $value): KeyValue
{
return new KeyValue([
'key' => $key,
'value' => $this->as_otlp_any_value($value),
]);
}
public function as_otlp_any_value($value): AnyValue
{
$result = new AnyValue();
switch (true) {
case is_array($value):
$values = [];
foreach ($value as $element) {
array_push($values, $this->as_otlp_any_value($element));
}
$result->setArrayValue(new ArrayValue(['values' => $values]));
break;
case is_int($value):
$result->setIntValue($value);
break;
case is_bool($value):
$result->setBoolValue($value);
break;
case is_double($value):
$result->setDoubleValue($value);
break;
case is_string($value):
$result->setStringValue($value);
break;
}
return $result;
}
public function as_otlp_span_kind($kind): int
{
switch ($kind) {
case 0: return SpanKind::SPAN_KIND_INTERNAL;
case 1: return SpanKind::SPAN_KIND_CLIENT;
case 2: return SpanKind::SPAN_KIND_SERVER;
case 3: return SpanKind::SPAN_KIND_PRODUCER;
case 4: return SpanKind::SPAN_KIND_CONSUMER;
}
return SpanKind::SPAN_KIND_UNSPECIFIED;
}
public function as_otlp_span(ReadableSpan $span): CollectorSpan
{
$end_timestamp = ($span->getStartEpochTimestamp() + $span->getDuration());
$parent_span = $span->getParent();
$parent_span_id = $parent_span ? $parent_span->getSpanId() : false;
$row = [
'trace_id' => hex2bin($span->getContext()->getTraceId()),
'span_id' => hex2bin($span->getContext()->getSpanId()),
'parent_span_id' => $parent_span_id ? hex2bin($parent_span_id) : null,
'name' => $span->getSpanName(),
'start_time_unix_nano' => $span->getStartEpochTimestamp(),
'end_time_unix_nano' => $end_timestamp,
'kind' => $this->as_otlp_span_kind($span->getSpanKind()),
// 'trace_state' => $span->getContext()
// 'links' =>
];
foreach ($span->getEvents() as $event) {
if (!array_key_exists('events', $row)) {
$row['events'] = [];
}
$attrs = [];
foreach ($event->getAttributes() as $k => $v) {
array_push($attrs, $this->as_otlp_key_value($k, $v->getValue()));
}
$row['events'][] = new Event([
'time_unix_nano' => $event->getTimestamp(),
'name' => $event->getName(),
'attributes' => $attrs,
]);
}
foreach ($span->getAttributes() as $k => $v) {
if (!array_key_exists('attributes', $row)) {
$row['attributes'] = [];
}
array_push($row['attributes'], $this->as_otlp_key_value($k, $v->getValue()));
}
$status = new Status();
switch ($span->getStatus()->getCanonicalStatusCode()) {
case SpanStatus::OK:
$status->setCode(StatusCode::STATUS_CODE_OK);
break;
case SpanStatus::ERROR:
$status->setCode(StatusCode::STATUS_CODE_ERROR)->setMessage($span->getStatus()->getStatusDescription());
break;
default:
$status->setCode(StatusCode::STATUS_CODE_UNSET);
}
$row['status'] = $status;
return new CollectorSpan($row);
}
// @return KeyValue[]
public function as_otlp_resource_attributes(iterable $spans): array
{
$attrs = [];
foreach ($spans as $span) {
foreach ($span->getResource()->getAttributes() as $k => $v) {
array_push($attrs, $this->as_otlp_key_value($k, $v->getValue()));
}
}
return $attrs;
}
public function as_otlp_resource_span(iterable $spans): ResourceSpans
{
// TODO: Should return an empty ResourceSpans when $spans is empty
// At the minute it returns an semi populated ResourceSpan
$ils = $convertedSpans = [];
foreach ($spans as $span) {
/** @var \OpenTelemetry\Sdk\InstrumentationLibrary $il */
$il = $span->getInstrumentationLibrary();
$ilKey = sprintf('%s@%s', $il->getName(), $il->getVersion()??'');
if (!isset($ils[$ilKey])) {
$convertedSpans[$ilKey] = [];
$ils[$ilKey] = new InstrumentationLibrary(['name' => $il->getName(), 'version' => $il->getVersion()??'']);
}
$convertedSpans[$ilKey][] = $this->as_otlp_span($span);
}
$ilSpans = [];
foreach ($ils as $ilKey => $il) {
$ilSpans[] = new InstrumentationLibrarySpans([
'instrumentation_library' => $il,
'spans' => $convertedSpans[$ilKey],
]);
}
return new Proto\Trace\V1\ResourceSpans([
'resource' => new Proto\Resource\V1\Resource([
'attributes' => $this->as_otlp_resource_attributes($spans),
]),
'instrumentation_library_spans' => $ilSpans,
]);
}
}

141
contrib/Zipkin/Exporter.php Normal file
View File

@ -0,0 +1,141 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Contrib\Zipkin;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use InvalidArgumentException;
use OpenTelemetry\Sdk\Trace;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Client\RequestExceptionInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
/**
* Class ZipkinExporter - implements the export interface for data transfer via Zipkin protocol
* @package OpenTelemetry\Exporter
*/
class Exporter implements Trace\Exporter
{
/**
* @var string
*/
private $endpointUrl;
/**
* @var SpanConverter
*/
private $spanConverter;
/**
* @var bool
*/
private $running = true;
/**
* @var ClientInterface
*/
private $client;
private $requestFactory;
private $streamFactory;
public function __construct(
$name,
string $endpointUrl,
ClientInterface $client,
RequestFactoryInterface $requestFactory,
StreamFactoryInterface $streamFactory,
SpanConverter $spanConverter = null
) {
$parsedDsn = parse_url($endpointUrl);
if (!is_array($parsedDsn)) {
throw new InvalidArgumentException('Unable to parse provided DSN');
}
if (
!isset($parsedDsn['scheme'])
|| !isset($parsedDsn['host'])
|| !isset($parsedDsn['port'])
|| !isset($parsedDsn['path'])
) {
throw new InvalidArgumentException('Endpoint should have scheme, host, port and path');
}
$this->endpointUrl = $endpointUrl;
$this->client = $client;
$this->requestFactory = $requestFactory;
$this->streamFactory = $streamFactory;
$this->spanConverter = $spanConverter ?? new SpanConverter($name);
}
/**
* Exports the provided Span data via the Zipkin protocol
*
* @param iterable<Trace\ReadableSpan> $spans Array of Spans
* @return int return code, defined on the Exporter interface
*/
public function export(iterable $spans): int
{
if (!$this->running) {
return Exporter::FAILED_NOT_RETRYABLE;
}
if (empty($spans)) {
return Trace\Exporter::SUCCESS;
}
$convertedSpans = [];
foreach ($spans as $span) {
array_push($convertedSpans, $this->spanConverter->convert($span));
}
try {
$body = $this->streamFactory->createStream(json_encode($convertedSpans));
$request = $this->requestFactory
->createRequest('POST', $this->endpointUrl)
->withBody($body)
->withHeader('content-type', 'application/json');
$response = $this->client->sendRequest($request);
} catch (RequestExceptionInterface $e) {
return Trace\Exporter::FAILED_NOT_RETRYABLE;
} catch (NetworkExceptionInterface | ClientExceptionInterface $e) {
return Trace\Exporter::FAILED_RETRYABLE;
}
if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 500) {
return Trace\Exporter::FAILED_NOT_RETRYABLE;
}
if ($response->getStatusCode() >= 500 && $response->getStatusCode() < 600) {
return Trace\Exporter::FAILED_RETRYABLE;
}
return Trace\Exporter::SUCCESS;
}
public function shutdown(): void
{
$this->running = false;
}
public static function fromConnectionString(string $endpointUrl, string $name, $args = null)
{
$factory = new HttpFactory();
$exporter = new Exporter(
$name,
$endpointUrl,
new Client(),
$factory,
$factory
);
return $exporter;
}
}

View File

@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Contrib\Zipkin;
use OpenTelemetry\Sdk\Trace\ReadableSpan;
class SpanConverter
{
const STATUS_CODE_TAG_KEY = 'op.status_code';
const STATUS_DESCRIPTION_TAG_KEY = 'op.status_description';
/**
* @var string
*/
private $serviceName;
public function __construct(string $serviceName)
{
$this->serviceName = $serviceName;
}
private function sanitiseTagValue($value)
{
// Casting false to string makes an empty string
if (is_bool($value)) {
return $value ? 'true' : 'false';
}
// Zipkin tags must be strings, but opentelemetry
// accepts strings, booleans, numbers, and lists of each.
if (is_array($value)) {
return join(',', array_map([$this, 'sanitiseTagValue'], $value));
}
// Floats will lose precision if their string representation
// is >=14 or >=17 digits, depending on PHP settings.
// Can also throw E_RECOVERABLE_ERROR if $value is an object
// without a __toString() method.
// This is possible because OpenTelemetry\Trace\Span does not verify
// setAttribute() $value input.
return (string) $value;
}
public function convert(ReadableSpan $span)
{
$spanParent = $span->getParent();
$row = [
'id' => $span->getContext()->getSpanId(),
'traceId' => $span->getContext()->getTraceId(),
'parentId' => $spanParent ? $spanParent->getSpanId() : null,
'localEndpoint' => [
'serviceName' => $this->serviceName,
],
'name' => $span->getSpanName(),
'timestamp' => (int) ($span->getStartEpochTimestamp() / 1e3), // RealtimeClock in microseconds
'duration' => (int) (($span->getEnd() - $span->getStart()) / 1e3), // Diff in microseconds
'tags' => [
self::STATUS_CODE_TAG_KEY => $span->getStatus()->getCanonicalStatusCode(),
self::STATUS_DESCRIPTION_TAG_KEY => $span->getStatus()->getStatusDescription(),
],
];
foreach ($span->getAttributes() as $k => $v) {
$row['tags'][$k] = $this->sanitiseTagValue($v->getValue());
}
foreach ($span->getEvents() as $event) {
if (!array_key_exists('annotations', $row)) {
$row['annotations'] = [];
}
$row['annotations'][] = [
'timestamp' => (int) ($event->getTimestamp() / 1e3), // RealtimeClock in microseconds
'value' => $event->getName(),
];
}
return $row;
}
}

View File

@ -0,0 +1,171 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Contrib\ZipkinToNewrelic;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use InvalidArgumentException;
use OpenTelemetry\Sdk\Trace;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Client\RequestExceptionInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
/**
* Class ZipkinExporter - implements the export interface for data transfer via Zipkin protocol
* @package OpenTelemetry\Exporter
*
* This is an experimental, non-supported exporter.
* It will send PHP Otel trace data end to end across the internet to a functional backend.
* Needs a license key to connect. For a free account/key, go to: https://newrelic.com/signup/
*/
class Exporter implements Trace\Exporter
{
/**
* @var string
*/
private $endpointUrl;
/**
* @var string
*/
private $licenseKey;
/**
* @var SpanConverter
*/
private $spanConverter;
/**
* @var bool
*/
private $running = true;
/**
* @var ClientInterface
*/
private $client;
/**
* @var RequestFactoryInterface
*/
private $requestFactory;
/**
* @var StreamFactoryInterface
*/
private $streamFactory;
/**
* @var string
*/
private $name;
public function __construct(
$name,
string $endpointUrl,
string $licenseKey,
ClientInterface $client,
RequestFactoryInterface $requestFactory,
StreamFactoryInterface $streamFactory,
SpanConverter $spanConverter = null
) {
$parsedDsn = parse_url($endpointUrl);
if (!is_array($parsedDsn)) {
throw new InvalidArgumentException('Unable to parse provided DSN');
}
if (
!isset($parsedDsn['scheme'])
|| !isset($parsedDsn['host'])
|| !isset($parsedDsn['path'])
) {
throw new InvalidArgumentException('Endpoint should have scheme, host, port and path');
}
$this->name = $name;
$this->endpointUrl = $endpointUrl;
$this->licenseKey = $licenseKey;
$this->client = $client;
$this->requestFactory = $requestFactory;
$this->streamFactory = $streamFactory;
$this->spanConverter = $spanConverter ?? new SpanConverter($name);
}
/**
* Exports the provided Span data via the Zipkin protocol
*
* @param iterable<Trace\ReadableSpan> $spans Array of Spans
* @return int return code, defined on the Exporter interface
*/
public function export(iterable $spans): int
{
if (!$this->running) {
return Exporter::FAILED_NOT_RETRYABLE;
}
if (empty($spans)) {
return Trace\Exporter::SUCCESS;
}
$convertedSpans = [];
foreach ($spans as $span) {
array_push($convertedSpans, $this->spanConverter->convert($span));
}
try {
$body = $this->streamFactory->createStream(json_encode($convertedSpans));
$request = $this->requestFactory
->createRequest('POST', $this->endpointUrl)
->withBody($body)
->withHeader('content-type', 'application/json')
->withAddedHeader('Api-Key', $this->licenseKey)
->withAddedHeader('Data-Format', 'zipkin')
->withAddedHeader('Data-Format-Version', '2');
$response = $this->client->sendRequest($request);
} catch (RequestExceptionInterface $e) {
return Trace\Exporter::FAILED_NOT_RETRYABLE;
} catch (NetworkExceptionInterface | ClientExceptionInterface $e) {
return Trace\Exporter::FAILED_RETRYABLE;
}
if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 500) {
return Trace\Exporter::FAILED_NOT_RETRYABLE;
}
if ($response->getStatusCode() >= 500 && $response->getStatusCode() < 600) {
return Trace\Exporter::FAILED_RETRYABLE;
}
return Trace\Exporter::SUCCESS;
}
public function shutdown(): void
{
$this->running = false;
}
public static function fromConnectionString(string $endpointUrl, string $name, $args)
{
if ($args == false) {
throw new Exception('Invalid license key.');
}
$factory = new HttpFactory();
$exporter = new Exporter(
$name,
$endpointUrl,
$args,
new Client(),
$factory,
$factory
);
return $exporter;
}
}

View File

@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Contrib\ZipkinToNewrelic;
use OpenTelemetry\Sdk\Trace\ReadableSpan;
class SpanConverter
{
const STATUS_CODE_TAG_KEY = 'otel.status_code';
const STATUS_DESCRIPTION_TAG_KEY = 'otel.status_description';
/**
* @var string
*/
private $serviceName;
public function __construct(string $serviceName)
{
$this->serviceName = $serviceName;
}
private function sanitiseTagValue($value)
{
// Casting false to string makes an empty string
if (is_bool($value)) {
return $value ? 'true' : 'false';
}
// Zipkin tags must be strings, but opentelemetry
// accepts strings, booleans, numbers, and lists of each.
if (is_array($value)) {
return join(',', array_map([$this, 'sanitiseTagValue'], $value));
}
// Floats will lose precision if their string representation
// is >=14 or >=17 digits, depending on PHP settings.
// Can also throw E_RECOVERABLE_ERROR if $value is an object
// without a __toString() method.
// This is possible because OpenTelemetry\Trace\Span does not verify
// setAttribute() $value input.
return (string) $value;
}
public function convert(ReadableSpan $span)
{
$spanParent = $span->getParent();
$row = [
'id' => $span->getContext()->getSpanId(),
'traceId' => $span->getContext()->getTraceId(),
'parentId' => $spanParent ? $spanParent->getSpanId() : null,
'localEndpoint' => [
'serviceName' => $this->serviceName,
],
'name' => $span->getSpanName(),
'timestamp' => (int) ($span->getStartEpochTimestamp() / 1e3), // RealtimeClock in microseconds
'duration' => (int) (($span->getEnd() - $span->getStart()) / 1e3), // Diff in microseconds
'tags' => [
self::STATUS_CODE_TAG_KEY => $span->getStatus()->getCanonicalStatusCode(),
self::STATUS_DESCRIPTION_TAG_KEY => $span->getStatus()->getStatusDescription(),
],
];
foreach ($span->getAttributes() as $k => $v) {
$row['tags'][$k] = $this->sanitiseTagValue($v->getValue());
}
foreach ($span->getEvents() as $event) {
if (!array_key_exists('annotations', $row)) {
$row['annotations'] = [];
}
$row['annotations'][] = [
'timestamp' => (int) ($event->getTimestamp() / 1e3), // RealtimeClock in microseconds
'value' => $event->getName(),
];
}
return $row;
}
}

View File

@ -1,14 +0,0 @@
deptrac:
skip_violations:
/src/Extension/Propagator/B3/_register.php:
- OpenTelemetry\SDK\Registry
/src/Extension/Propagator/CloudTrace/_register.php:
- OpenTelemetry\SDK\Registry
/src/Extension/Propagator/Jaeger/_register.php:
- OpenTelemetry\SDK\Registry
OpenTelemetry\API\Configuration\Config\ComponentProvider:
- Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition
- Symfony\Component\Config\Definition\Builder\NodeBuilder
OpenTelemetry\API\Configuration\Config\ComponentProviderRegistry:
- Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition
- Symfony\Component\Config\Definition\Builder\NodeDefinition

View File

@ -1,168 +0,0 @@
imports:
- deptrac.baseline.yaml
deptrac:
analyser:
types:
- class
- class_superglobal
- use
- file
- function
- function_superglobal
paths:
- ./src
- ./proto
- ./tests
- ./deptrac/polyfills
- ./vendor/symfony/polyfill-php83/Resources/stubs
exclude_files:
- '#.*test.*#'
layers:
- name: API
collectors:
- type: directory
value: src/API/.*
- name: SDK
collectors:
- type: directory
value: src/SDK/.*
- name: ConfigSDK
collectors:
- type: directory
value: src/Config/SDK/.*
- name: Context
collectors:
- type: directory
value: src/Context/.*
- name: SemConv
collectors:
- type: directory
value: src/SemConv/.*
- name: Contrib
collectors:
- type: directory
value: src/Contrib/.*
- name: Extension
collectors:
- type: directory
value: src/Extension/.*
- name: OtelProto
collectors:
- type: directory
value: proto/otel/.*
- name: GoogleProtobuf
collectors:
- type: className
regex: ^Google\\Protobuf\\*
- name: Grpc
collectors:
- type: className
regex: ^Grpc\\*
- name: PsrLog
collectors:
- type: className
regex: ^Psr\\Log\\*
- name: PsrHttp
collectors:
- type: className
regex: ^Psr\\Http\\*
- name: HttpPlug
collectors:
- type: className
regex: ^Http\\*
- name: Prometheus
collectors:
- type: className
regex: ^Prometheus\\*
- name: FFI
collectors:
- type: className
regex: ^FFI\\*
- name: Composer
collectors:
- type: className
regex: ^Composer\\*
- name: HttpClients
collectors:
- type: className
value: ^Symfony\\Component\\HttpClient\\*
- type: className
value: ^GuzzleHttp\\*
- type: className
value: ^Buzz\\*
- name: SPI
collectors:
- type: className
value: ^Nevay\\SPI\\*
- name: SymfonyConfig
collectors:
- type: className
value: ^Symfony\\Component\\Config\\*
- type: className
value: ^Symfony\\Component\\Yaml\\*
- type: className
value: ^Symfony\\Component\\VarExporter\\*
- name: RamseyUuid
collectors:
- type: className
regex: ^Ramsey\\Uuid\\*
- name: NyholmPsr7Server
collectors:
- type: className
regex: ^Nyholm\\Psr7Server\\*
- name: Polyfills
collectors:
- type: directory
value: deptrac/polyfills/.*
- type: directory
value: vendor/symfony/polyfill-php83
- name: DotenvProvider
collectors:
- type: className
regex: ^Symfony\\Component\\Dotenv\\*
- type: className
regex: ^Dotenv\\*
ruleset:
Context:
- FFI
- Polyfills
SemConv: ~
ConfigSDK:
- SymfonyConfig
- API
- SDK
- SPI
- PsrLog
- Composer
- Context
- Contrib
- Extension
- Polyfills
- DotenvProvider
API:
- Context
- PsrLog
- SPI
- Polyfills
SDK:
- +API
- ConfigSDK
- SemConv
- PsrHttp
- HttpPlug
- Composer
- HttpClients
- SPI
- RamseyUuid
- NyholmPsr7Server
Contrib:
- +SDK
- +OtelProto
- Grpc
- Prometheus
Extension:
- +API
OtelProto:
- GoogleProtobuf
- Grpc

View File

@ -1,5 +0,0 @@
<?php
namespace OpenTelemetry\Config\SDK\Configuration;
class ComponentPlugin {}

View File

@ -1,5 +0,0 @@
<?php
namespace OpenTelemetry\Config\SDK\Configuration;
class ComponentProvider {}

View File

@ -1,5 +0,0 @@
<?php
namespace OpenTelemetry\Config\SDK\Configuration;
class ComponentProviderRegistry {}

View File

@ -1,5 +0,0 @@
<?php
namespace OpenTelemetry\Config\SDK\Configuration;
class Context {}

View File

@ -1,3 +0,0 @@
Polyfills for deptrac, which represent classes missing from some PHP versions, or classes
that deptrac does not understand.
We also use some symfony polyfills for deptrac, under `vendor/symfony/polyfill-*`.

View File

@ -1,11 +1,13 @@
version: '3.7'
services:
php:
image: ghcr.io/open-telemetry/opentelemetry-php/opentelemetry-php-base:${PHP_VERSION:-8.1}
build:
context: .
dockerfile: docker/Dockerfile
volumes:
- ./:/usr/src/myapp
depends_on:
- collector
- otel-collector
zipkin:
image: openzipkin/zipkin-slim
ports:
@ -17,7 +19,8 @@ services:
ports:
- 9412:9412
- 16686:16686
collector:
otel-collector:
platform: linux/amd64
image: otel/opentelemetry-collector-contrib
command: ["--config=/etc/otel-collector-config.yml"]
volumes:
@ -29,5 +32,5 @@ services:
- "13133:13133" # health_check extension
- "9411" # Zipkin receiver
- "4317:4317" # OTLP gRPC receiver
- "4318:4318" # OTLP/HTTP receiver
- "55681:55681" # Legacy OTLP/HTTP Port
- "55680:55679" # zpages extension

View File

@ -1,26 +0,0 @@
version: '3.8'
services:
web:
image: nginx:alpine
ports:
- '8080:80'
depends_on:
- php
volumes:
- ./docker/examples/nginx.conf:/etc/nginx/conf.d/default.conf:ro
php:
build:
context: .
dockerfile: docker/examples/Dockerfile
args:
- PHP_VERSION=8.1-fpm
- EXT_ENABLE=ffi
environment:
- OTEL_PHP_FIBERS_ENABLED=true
command:
- php-fpm
- -d
- opcache.preload=/php/vendor/autoload.php
volumes:
- ./tests/Context/Fiber/test_context_switching_ffi_observer.phpt:/var/www/public/index.php
- ./:/php

View File

@ -1,11 +0,0 @@
services:
phpdoc:
image: phpdoc/phpdoc:3
volumes:
- ./:/data
preview:
image: nginx:alpine
ports:
- 8080:80
volumes:
- ./docs/build:/usr/share/nginx/html

View File

@ -15,19 +15,16 @@ services:
ports:
- 8080:80
depends_on:
- php
- php-prometheus
volumes:
- ./docker/examples/nginx.conf:/etc/nginx/conf.d/default.conf:ro
php:
- ./docker/prometheus/nginx.conf:/etc/nginx/conf.d/default.conf
- ./docker/prometheus/index.php:/var/www/public/index.php
php-prometheus:
build:
context: .
dockerfile: docker/examples/Dockerfile
args:
- PHP_VERSION=8.0-fpm
- EXT_ENABLE=redis
dockerfile: docker/prometheus/Dockerfile
volumes:
- ./examples/prometheus/index.php:/var/www/public/index.php
- ./:/php
- ./:/var/www/public
depends_on:
- redis
- prometheus
- prometheus

View File

@ -0,0 +1,7 @@
version: '3.7'
services:
proto:
image: socialpoint/protobuf-tools
volumes:
- ./:/mnt
command: sh -c "/mnt/script/proto_gen.sh"

View File

@ -1,11 +1,9 @@
version: '3.7'
services:
php:
image: ghcr.io/open-telemetry/opentelemetry-php/opentelemetry-php-base:${PHP_VERSION:-8.1}
build:
context: .
dockerfile: docker/Dockerfile
volumes:
- ./:/usr/src/myapp
- ./:/usr/src/open-telemetry/
user: "${PHP_USER}:root"
environment:
XDEBUG_MODE: ${XDEBUG_MODE:-off}
XDEBUG_CONFIG: ${XDEBUG_CONFIG:-''}

View File

@ -1,11 +1,11 @@
version: '3.7'
services:
php:
image: ghcr.io/open-telemetry/opentelemetry-php/opentelemetry-php-base:${PHP_VERSION:-8.1}
build:
context: .
dockerfile: docker/Dockerfile
volumes:
- ./:/usr/src/myapp
user: "${PHP_USER}:root"
env_file:
- .env
zipkin:
image: openzipkin/zipkin-slim
ports:
@ -17,8 +17,3 @@ services:
ports:
- 9412:9412
- 16686:16686
collector:
image: otel/opentelemetry-collector-contrib
command: [ "--config=/etc/otel-collector-config.yml" ]
volumes:
- ./files/collector/otel-collector-config.yml:/etc/otel-collector-config.yml

View File

@ -1,46 +1,17 @@
FROM composer:2.8 AS composer
FROM debian:bullseye
FROM php:8-buster
RUN apt-get -y update && apt-get -y install git zip && \
curl -sS https://getcomposer.org/installer | php && \
mv composer.phar /usr/local/bin/composer && \
chmod +x /usr/local/bin/composer && \
pecl install ast-1.0.10 xdebug && \
docker-php-ext-enable ast xdebug && \
# The pcntl extension is used for speeding up `make phan`
docker-php-ext-install pcntl
# install GRPC
RUN apt install -y --no-install-recommends zlib1g-dev && \
pecl install grpc && \
docker-php-ext-enable grpc
WORKDIR /usr/src/myapp
RUN apt-get update \
&& apt-get install -y --no-install-recommends git wget gnupg2 \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd --gid 1000 php \
&& useradd --system --uid 1000 --gid php --shell /bin/bash --create-home php
RUN apt-get update && apt-get install -y lsb-release apt-transport-https ca-certificates \
&& echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list \
&& wget -qO - https://packages.sury.org/php/apt.gpg | apt-key add - \
&& apt-get update
ARG PHP_VERSION=8.3
RUN apt-get install -y \
php${PHP_VERSION}-ast \
php${PHP_VERSION}-cli \
php${PHP_VERSION}-curl \
php${PHP_VERSION}-dev \
php${PHP_VERSION}-grpc \
php${PHP_VERSION}-intl \
php${PHP_VERSION}-mbstring \
php${PHP_VERSION}-opcache \
php${PHP_VERSION}-opentelemetry \
php${PHP_VERSION}-protobuf \
php${PHP_VERSION}-simplexml \
php${PHP_VERSION}-sockets \
php${PHP_VERSION}-xdebug \
php${PHP_VERSION}-zip \
php${PHP_VERSION}-mongodb \
php${PHP_VERSION}-amqp \
php${PHP_VERSION}-rdkafka \
php${PHP_VERSION}-mysqli \
php${PHP_VERSION}-pgsql \
unzip
COPY --from=composer /usr/bin/composer /usr/local/bin/composer
RUN echo "grpc.enable_fork_support = 1" > $(php-config --ini-dir)/40-otel-dev.ini \
&& echo "grpc.poll_strategy = epoll1" >> $(php-config --ini-dir)/40-otel-dev.ini \
&& echo "zend.assertions = 1" >> $(php-config --ini-dir)/40-otel-dev.ini
USER php

View File

@ -1,34 +0,0 @@
ARG PHP_VERSION
ARG PHP_ALPINE_VERSION
FROM php:${PHP_VERSION:+${PHP_VERSION}-}alpine${PHP_ALPINE_VERSION} AS php
ENV PROJECT_ROOT=/php
RUN set -eux; \
apk add --no-cache \
bash \
; \
adduser -u 1000 -DSh /php php php
ARG EXT_INSTALL
ARG EXT_ENABLE=''
RUN --mount=from=mlocati/php-extension-installer,dst=/build/extension-installer,src=/usr/bin/install-php-extensions \
set -eux; \
/build/extension-installer \
opcache \
${EXT_INSTALL:-${EXT_ENABLE}} \
; \
docker-php-ext-enable \
opcache \
${EXT_ENABLE} \
;
RUN set eux; \
mkdir -p /usr/local/lib/php/vendor; \
echo "<?php file_exists('/php/vendor/autoload.php') and require '/php/vendor/autoload.php';" > /usr/local/lib/php/vendor/autoload.php; \
echo 'auto_prepend_file=vendor/autoload.php' > "$PHP_INI_DIR/conf.d/99-autoload.ini"
USER php
WORKDIR /php

View File

@ -1,12 +0,0 @@
server {
listen 80;
server_name _;
root /var/www/public;
location / {
fastcgi_pass php:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}

View File

@ -1,9 +0,0 @@
version: '3.7'
services:
gitsplit:
image: jderusse/gitsplit
volumes:
- ../../:/srv
- ../../var/cache/gitsplit:/cache/gitsplit
environment:
GH_TOKEN: ${GITSPLIT_TOKEN:-''}

View File

@ -0,0 +1,11 @@
FROM php:7.3-fpm-alpine
RUN apk update && apk add --no-cache \
$PHPIZE_DEPS
RUN pecl channel-update pecl.php.net && \
pecl install redis \
&& docker-php-ext-enable redis
# Clean
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /var/cache/*

25
docker/prometheus/nginx.conf Executable file
View File

@ -0,0 +1,25 @@
server {
listen 80;
index index.php index.html;
server_name 127.0.0.1 localhost;
root /var/www/public/examples/prometheus;
access_log /dev/stdout;
error_log /dev/stdout debug;
underscores_in_headers on;
location / {
try_files $uri /index.php?$args;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php-prometheus:9000;
fastcgi_index index.php;
fastcgi_read_timeout 1000;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}

256
docs/laravel-integration.md Normal file
View File

@ -0,0 +1,256 @@
# Integrating Opentelemetry PHP into Laravel Applications
## Introduction
Distributed tracing helps developers and management gain insights into how well applications perform in terms of traces, metrics, and logs. This guide shows how developers can integrate OpenTelemetry PHP into their Laravel applications for the above benefits. Our example application visualizes exceptions from a Laravel application using both Jaeger and Zipkin.
To follow this guide you will need:
* PHP Installed; this example uses PHP 7.4.
* [Composer](https://getcomposer.org/download/ ) for dependency management.
* [Docker](https://docs.docker.com/get-docker/) for bundling our visualization tools. We have setup instructions for docker on this project's [readme](https://github.com/open-telemetry/opentelemetry-php#development).
This example uses Laravel version 8.40 .
## Step 1 - Creating a Laravel Application
The Laravel framework supports creating applications using composer. To do that, run `composer create-project <project-name>` . We are naming our project `otel-php-laravel-basic-example`, so the command is as follows:
`composer create-project laravel/laravel otel-php-laravel-basic-example`
To confirm that our application works, we can move to the application directory using `cd otel-php-laravel-basic-example` , then serve the application with `php artisan serve` .
![image](https://user-images.githubusercontent.com/22311928/115635306-5585e600-a303-11eb-8943-f50846b293b3.png)
Let's navigate to `http://127.0.0.1:8000` on our browser to see the default Laravel welcome page.
![image](https://user-images.githubusercontent.com/22311928/115635309-56b71300-a303-11eb-97bc-7c64e3f4da97.png)
## Step 2 - Require OpenTelemetry PHP Package
Starting from version `v.0.0.2`, the open-telemetry php package allows users to use their preferred HTTP layers for exporting traces. The benefit of this is that users can reuse already existing HTTP configurations for their applications. Hence, there is need to require packages that satisfy both `psr/http-client-implementation` and `psr/http-factory-implementation` before requiring the opentelemetry-php package.
By default, the Laravel framework utilizes `guzzlehttp/guzzle` and this satisfies `psr/http-client-implementation`, so we need to require the `guzzlehttp/psr7` to meet the `psr/http-factory-implementation` requirement. Let's run `composer require guzzlehttp/psr7:2.0.0-rc1`.
Note: We are specifying `2.0.0-rc1` as that is the release for `guzzlehttp/psr7` that includes HTTP factories as at the time of writing this guide.
Next, let's run `composer require open-telemetry/opentelemetry` to pull in the openTelemetry-php package.
## Step 3 - Bundle Zipkin and Jaeger into the Application
To visualize traces exported from our application, we need to integrate open source tracing tools [Zipkin](https://zipkin.io/) and [Jaeger](https://www.jaegertracing.io/) into our setup using docker.
First, we create a `docker-compose.yaml` file in the root of our project, with content as follows:
```yaml
version: '3.7'
services:
zipkin:
image: openzipkin/zipkin-slim
ports:
- 9411:9411
jaeger:
image: jaegertracing/all-in-one
environment:
COLLECTOR_ZIPKIN_HOST_PORT: 9412
ports:
- 9412:9412
- 16686:16686
```
Next, we pull in Zipkin and Jaeger by running `docker-compose up -d`. This might take some time, depending on your internet connection speed.
![image](https://user-images.githubusercontent.com/22311928/115635312-5880d680-a303-11eb-9f55-bcd80115abc9.png)
We can confirm that Zipkin is up by navigating to `http://localhost:9411/` on our browser. For Jaeger, navigating to `http://localhost:16686/` on our browser should display the Jaeger home page.
![image](https://user-images.githubusercontent.com/22311928/115635313-59196d00-a303-11eb-817c-37f4f6416fe1.png)
![image](https://user-images.githubusercontent.com/22311928/115635316-59b20380-a303-11eb-86e4-e15d0efd04d7.png)
## Step 5 - Instrument Laravel Application
For this step, we will utilize our OpenTelemetry PHP Library to export traces to both Zipkin and Jaeger.
The default entry point for Laravel applications is the `index.php` file located in the `public` folder. If we navigate to `public\index.php` we can see that the index file autoloads classes from packages within our vendor folder, making them easily useable within our application.
```php
require __DIR__.'/../vendor/autoload.php';
```
The other parts of the `index.php` file enable request and response resolution using the application kernel.
```php
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Kernel::class);
$response = tap($kernel->handle(
$request = Request::capture()
))->send();
$kernel->terminate($request, $response);
```
It is worthy of note that resources(namespaces, classes, variables) created within the `index.php` file are available within the entire application.
To use open-telemetry specific classes within our application we have to import them at the top of our index file, using the `use` keyword. This is what our list of open-telemetry imported classes should look like:
```php
use OpenTelemetry\Contrib\Jaeger\Exporter as JaegerExporter;
use OpenTelemetry\Contrib\Zipkin\Exporter as ZipkinExporter;
use OpenTelemetry\Sdk\Trace\Clock;
use OpenTelemetry\Context\Context;
use OpenTelemetry\Sdk\Trace\Sampler\AlwaysOnSampler;
use OpenTelemetry\Sdk\Trace\SamplingResult;
use OpenTelemetry\Sdk\Trace\SpanProcessor\BatchSpanProcessor;
use OpenTelemetry\Sdk\Trace\TracerProvider;
use OpenTelemetry\Trace as API;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
```
Remember that these imports should go side by side with the default class imports that come with the `index.php` file.
Next, we create a sample recording trace using the [AlwaysOnSampler](https://github.com/open-telemetry/opentelemetry-php/blob/main/sdk/Trace/Sampler/AlwaysOnSampler.php) class, just before the app instance is created like below:
```php
$sampler = new AlwaysOnSampler();
$samplingResult = $sampler->shouldSample(
new Context(),
md5((string) microtime(true)),
'io.opentelemetry.example',
API\SpanKind::KIND_INTERNAL
);
```
Since we are looking to export traces to both Zipkin and Jaeger we have to make use of their exporters;
```php
$jaegerExporter = new JaegerExporter(
'Hello World Web Server Jaeger',
'http://localhost:9412/api/v2/spans',
new Client(),
new HttpFactory(),
new HttpFactory()
);
$zipkinExporter = new ZipkinExporter(
'Hello World Web Server Zipkin',
'http://localhost:9411/api/v2/spans',
new Client(),
new HttpFactory(),
new HttpFactory()
);
```
Next, we create a trace then add processors for each trace(One for Jaeger and another for Zipkin). Then we proceed to start and activate a span for each trace. We create a trace only if the RECORD AND SAMPLED sampling result condition passes as follows;
```php
if (SamplingResult::RECORD_AND_SAMPLE === $samplingResult->getDecision()) {
$jaegerTracer = (new TracerProvider(null, $sampler))
->addSpanProcessor(new BatchSpanProcessor($jaegerExporter, Clock::get()))
->getTracer('io.opentelemetry.contrib.php');
$zipkinTracer = (new TracerProvider(null, $sampler))
->addSpanProcessor(new BatchSpanProcessor($zipkinExporter, Clock::get()))
->getTracer('io.opentelemetry.contrib.php');
$request = Request::createFromGlobals();
$jaegerSpan = $jaegerTracer->startAndActivateSpan($request->getUri());
$zipkinSpan = $zipkinTracer->startAndActivateSpan($request->getUri());
}
```
Finally, we end the active spans if sampling is complete, by adding the following block at the end of the `index.php` file;
```php
if (SamplingResult::RECORD_AND_SAMPLE === $samplingResult->getDecision()) {
$zipkinSpan->end();
$jaegerSpan->end();
}
```
Let's confirm that we can see exported traces on both Zipkin and Jaeger. To do that, we need to reload `http://127.0.0.1:8000` on our browser;
We also need reload both Zipkin and Jaeger on our browser, using the URLs `http://localhost:9411/` and `http://localhost:16686/`. Do ensure that both your Laravel server and docker instance are running for this step.
For Jaeger under service, you should see a `Hello World Web Server Jaeger` service. Go ahead and click find traces to see exported traces.
![image](https://user-images.githubusercontent.com/22311928/115635317-5a4a9a00-a303-11eb-9840-2dba0e6475d8.png)
Once we click on `Find Traces`, you should be able to see traces like below:
![image](https://user-images.githubusercontent.com/22311928/115635318-5a4a9a00-a303-11eb-9d4e-69e45a3b810c.png)
We can click on a trace to get more information about the trace.
![image](https://user-images.githubusercontent.com/22311928/115635320-5ae33080-a303-11eb-8956-97d9f1ce0073.png)
For Zipkin, we can visualize our trace by clicking on `Run Query`
![image](https://user-images.githubusercontent.com/22311928/115635321-5b7bc700-a303-11eb-9d9d-19168e50445e.png)
Since resources in Laravel's `public\index.php` file are available to the entire application, we can use any of the already instantiated tracers to further instrument controllers or any other parts of our application.
Let's create a `Hello` controller to check this out. Run the command `php artisan make:controller HelloController`
![image](https://user-images.githubusercontent.com/22311928/115635322-5c145d80-a303-11eb-8071-1453edbaca14.png)
Next we need to add a route for accessing the controller. To do this we need to utilize the `HelloController` class within our web routes file located in the `routes\web.php` as follows:
```php
use App\Http\Controllers\HelloController;
```
Next we need to add a route and method for the controller.
```php
Route::get('/hello', [HelloController::class, 'index']);
```
The above snippet routes every GET request from the `/hello` route on the browser to an index method within the `HelloController` class. For now, this method does not exist, so we have to add it to our controller as follows
```php
public function index(){
return "hello";
}
```
Let's confirm that everything works well by visiting the `/hello` route on our browser.
![](https://user-images.githubusercontent.com/22311928/115635323-5c145d80-a303-11eb-869f-6fe18f7f01a4.png)
Now that we have the `index` method working, we can simulate adding an exception event to our Zipkin trace as follows:
```php
global $zipkinTracer;
if ($zipkinTracer) {
/** @var Span $span */
$span = $zipkinTracer->getActiveSpan();
$span->setAttribute('foo', 'bar');
$span->updateName('New name');
$childSpan = $zipkinTracer->startAndActivateSpan('Child span');
try {
throw new \Exception('Exception Example');
} catch (\Exception $exception) {
$span->setSpanStatus($exception->getCode(), $exception->getMessage());
}
$childSpan->end();
}
```
In the above snippet we change the span name and attributes for our Zipkin trace, we also add an exception event to the span.
We need to reload our `http://127.0.0.1:8000/hello` route, then navigate to Zipkin like before, to see that our span name gets updated to `new name` and our `Exception Example` is visible.
![image](https://user-images.githubusercontent.com/22311928/115635324-5cacf400-a303-11eb-947d-cf8205c0e93b.png)
## Summary
With the above example we have been able to instrument a Laravel application using the OpenTelemetry PHP library. You can fork the example project [here](https://github.com/prondubuisi/otel-php-laravel-basic-example).

View File

@ -1,291 +0,0 @@
# Exploring Opentelemetry in Laravel Applications
## Introduction
Distributed tracing helps developers and management gain insights into how well applications perform in terms of traces,
metrics, and logs. This guide shows how developers can integrate OpenTelemetry PHP into their Laravel applications for
the above benefits. Our example application visualizes exceptions from a Laravel application using both Jaeger and
Zipkin.
To follow this guide you will need:
* PHP Installed; this example uses PHP 7.4.
* [Composer](https://getcomposer.org/download/ ) for dependency management.
* [Docker](https://docs.docker.com/get-docker/) for bundling our visualization tools. We have setup instructions for
docker on this project's [readme](https://github.com/open-telemetry/opentelemetry-php#development).
This example uses Laravel version `9.1` and OpenTelemetry PHP SDK version `0.0.11`.
> ⚠ This example is only intended to introduce how OpenTelemetry can be used in a Laravel application. The
> example code is not suited for production applications, and must not be consulted for any code that goes into
> production.
## Step 1 - Creating a Laravel Application
The Laravel framework supports creating applications using composer. To do that,
run `composer create-project <project-name>`. We are naming our project `otel-php-laravel-basic-example`, so the
command is as follows:
`composer create-project laravel/laravel otel-php-laravel-basic-example`
To confirm that our application works, we can move to the application directory
using `cd otel-php-laravel-basic-example` , then serve the application with `php artisan serve` .
![image](https://user-images.githubusercontent.com/22311928/115635306-5585e600-a303-11eb-8943-f50846b293b3.png)
Let's navigate to `http://127.0.0.1:8000` on our browser to see the default Laravel welcome page.
![image](https://user-images.githubusercontent.com/22311928/115635309-56b71300-a303-11eb-97bc-7c64e3f4da97.png)
## Step 2 - Require OpenTelemetry PHP Package
Starting from version `v.0.0.2`, the open-telemetry php package allows users to use their preferred HTTP layers for
exporting traces. The benefit of this is that users can reuse already existing HTTP configurations for their
applications. Hence, there is need to require packages that satisfy both `psr/http-client-implementation`
and `psr/http-factory-implementation` before requiring the opentelemetry-php package.
By default, the Laravel framework utilizes `guzzlehttp/guzzle` and this satisfies `psr/http-client-implementation`, so
we need to require the `guzzlehttp/psr7` to meet the `psr/http-factory-implementation` requirement. Let's
run `composer require guzzlehttp/psr7:2.1`.
Note: We are specifying `2.1` as that is the release for `guzzlehttp/psr7` that includes HTTP factories as at the
time of writing this guide.
Starting from version `v.0.0.4`, opentelemetry-php package also requires `php-http/async-client-implementation`. Any
client implementation would work, but for this example, let's go ahead with `php-http/guzzle7-adapter`. Run
`composer require php-http/guzzle7-adapter` to install `php-http/guzzle7-adapter`.
Next, let's run `composer require open-telemetry/opentelemetry` to pull in the openTelemetry-php package. It is worthy
of note that this command pulls in the last stable release for the library.
## Step 3 - Bundle Zipkin and Jaeger into the Application
To visualize traces exported from our application, we need to integrate open source tracing
tools [Zipkin](https://zipkin.io/) and [Jaeger](https://www.jaegertracing.io/) into our setup using docker.
First, we create a `docker-compose.yaml` file in the root of our project, with content as follows:
```yaml
version: '3.7'
services:
zipkin:
image: openzipkin/zipkin-slim
ports:
- "9411:9411"
jaeger:
image: jaegertracing/all-in-one
environment:
COLLECTOR_ZIPKIN_HOST_PORT: 9412
ports:
- "9412:9412"
- "16686:16686"
```
Next, we pull in Zipkin and Jaeger by running `docker-compose up -d`. This might take some time, depending on your
internet connection speed.
![image](https://user-images.githubusercontent.com/22311928/115635312-5880d680-a303-11eb-9f55-bcd80115abc9.png)
We can confirm that Zipkin is up by navigating to `http://localhost:9411/` on our browser. For Jaeger, navigating
to `http://localhost:16686/` on our browser should display the Jaeger home page.
![image](https://user-images.githubusercontent.com/22311928/115635313-59196d00-a303-11eb-817c-37f4f6416fe1.png)
![image](https://user-images.githubusercontent.com/22311928/115635316-59b20380-a303-11eb-86e4-e15d0efd04d7.png)
## Step 5 - Instrument Laravel Application
For this step, we will utilize our OpenTelemetry PHP Library to export traces to both Zipkin and Jaeger.
The default entry point for Laravel applications is the `index.php` file located in the `public` folder. If we navigate
to `public\index.php` we can see that the index file autoloads classes from packages within our vendor folder, making
them easily usable within our application.
```php
require __DIR__.'/../vendor/autoload.php';
```
The other parts of the `index.php` file enable request and response resolution using the application kernel.
```php
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Kernel::class);
$response = tap($kernel->handle(
$request = Request::capture()
))->send();
$kernel->terminate($request, $response);
```
It is worthy of note that resources(namespaces, classes, variables) created within the `index.php` file are available
within the entire application.
To use open-telemetry specific classes within our application we have to import them at the top of our index file, using
the `use` keyword. This is what our list of open-telemetry imported classes should look like:
```php
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use OpenTelemetry\API\Trace\Span;
use OpenTelemetry\API\Trace\StatusCode;
use OpenTelemetry\API\Trace\TracerInterface;
use OpenTelemetry\Contrib\Zipkin\Exporter as ZipkinExporter;
use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
```
Remember that these imports should go side by side with the default class imports that come with the `index.php` file.
Next, we create a sample recording trace using
the [AlwaysOnSampler](https://github.com/open-telemetry/opentelemetry-php/blob/main/sdk/Trace/Sampler/AlwaysOnSampler.php)
class, which is default. Since we are looking to export traces to both Zipkin and Jaeger, we configure a tracer with exporters for both the
services just before the app instance is created like below;
```php
$httpClient = new Client();
$httpFactory = new HttpFactory();
$tracer = (new TracerProvider(
[
new SimpleSpanProcessor(
new OpenTelemetry\Contrib\Zipkin\Exporter(
PsrTransportFactory::discover()->create('http://zipkin:9411/api/v2/spans', 'application/json')
),
),
],
new AlwaysOnSampler(),
))->getTracer('Hello World Laravel Web Server');
```
Next we create a span from our tracer;
```php
$request = Request::capture();
$span = $tracer->spanBuilder($request->url())->startSpan();
$spanScope = $span->activate();
```
Finally, we end the active spans once and detach the span scope by adding the following block at the end of
the `index.php` file;
```php
$span->end();
$spanScope->detach();
```
Let's confirm that we can see exported traces on both Zipkin and Jaeger. To do that, we need to
reload `http://127.0.0.1:8000` on our browser;
We also need reload both Zipkin and Jaeger on our browser, using the URLs `http://localhost:9411/`
and `http://localhost:16686/`. Do ensure that both your Laravel server and docker instance are running for this step.
For Jaeger under service, you should see a `Hello World Web Server Jaeger` service. Go ahead and click find traces to
see exported traces.
![image](https://user-images.githubusercontent.com/22311928/115635317-5a4a9a00-a303-11eb-9840-2dba0e6475d8.png)
Once we click on `Find Traces`, you should be able to see traces like below:
![image](https://user-images.githubusercontent.com/22311928/115635318-5a4a9a00-a303-11eb-9d4e-69e45a3b810c.png)
We can click on a trace to get more information about the trace.
![image](https://user-images.githubusercontent.com/22311928/115635320-5ae33080-a303-11eb-8956-97d9f1ce0073.png)
For Zipkin, we can visualize our trace by clicking on `Run Query`
![image](https://user-images.githubusercontent.com/22311928/115635321-5b7bc700-a303-11eb-9d9d-19168e50445e.png)
Since resources in Laravel's `public\index.php` file are available to the entire application, we can use any of the
already instantiated tracers to further instrument controllers or any other parts of our application.
Let's create a `Hello` controller to check this out. Run the command `php artisan make:controller HelloController`
![image](https://user-images.githubusercontent.com/22311928/115635322-5c145d80-a303-11eb-8071-1453edbaca14.png)
Next we need to add a route for accessing the controller. To do this we need to utilize the `HelloController` class
within our web routes file located in the `routes\web.php` as follows:
```php
use App\Http\Controllers\HelloController;
```
Next we need to add a route and method for the controller.
```php
Route::get('/hello', [HelloController::class, 'index']);
```
The above snippet routes every GET request from the `/hello` route on the browser to an index method within
the `HelloController` class. For now, this method does not exist, so we have to add it to our controller in
`app\Http\Controllers\HelloController.php` as follows:
```php
public function index(){
return "hello";
}
```
Let's confirm that everything works well by visiting the `/hello` route on our browser.
![](https://user-images.githubusercontent.com/22311928/115635323-5c145d80-a303-11eb-869f-6fe18f7f01a4.png)
Now that we have the `index` method working, let's make changes to the existing rootSpan and create an exception in a
child span of the rootSpan. We can do the same as follows:
- Import the required functions on top of the file:
```php
use OpenTelemetry\API\Trace\Span;
use OpenTelemetry\API\Trace\StatusCode;
use OpenTelemetry\SDK\Trace\TracerProvider;
```
- Put the below snippet in `index()` function before the `return` statement:
```php
/** @var TracerProvider $tracer */
$tracer = TracerProvider::getDefaultTracer();
if ($tracer) {
/** @var Span $span */
$span = Span::getCurrent();
$span->setAttribute('foo', 'bar');
$span->setAttribute('Application', 'Laravel');
$span->setAttribute('foo', 'bar1');
$span->updateName('New name');
$childSpan = $tracer->spanBuilder('Child span')->startSpan();
$childScope = $childSpan->activate();
try {
throw new \Exception('Exception Example');
} catch (\Exception $exception) {
$childSpan->recordException($exception);
}
$childSpan->end();
$childScope->detach();
}
```
In the above snippet, we set two new attributes for the current span, and then change the value for one of the attribute
and the trace reflects the latest value. We also change the name of our current span and add an exception event to the
child span.
* Point to note: all the variables in `index.php` are available in this file. But we use inbuilt functions to get `$tracer`
rather than using the global variables.
We need to reload our `http://127.0.0.1:8000/hello` route, then navigate to Zipkin and Jaeger like before, to see that our
span name gets updated to `new name` and our `Exception Example` is visible.
![image](https://user-images.githubusercontent.com/22311928/115635324-5cacf400-a303-11eb-947d-cf8205c0e93b.png)
## Summary
With the above example we have been able to instrument a Laravel application using the OpenTelemetry PHP library. You
can fork the example project [here](https://github.com/prondubuisi/otel-php-laravel-basic-example).

Some files were not shown because too many files have changed in this diff Show More