mirror of
https://github.com/docker/cli.git
synced 2026-08-24 10:05:37 -05:00
Compare commits
@@ -5,7 +5,7 @@ updates:
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- "area/testing"
|
||||
- "area/ci"
|
||||
- "status/2-code-review"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
+19
-14
@@ -35,7 +35,9 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
-
|
||||
name: Create matrix
|
||||
id: platforms
|
||||
@@ -63,10 +65,10 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
|
||||
uses: docker/bake-action@6614cfa25eff9a0b2b2697efb0b6159e7680d584 # v7.2.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
set: |
|
||||
@@ -78,17 +80,18 @@ jobs:
|
||||
working-directory: ./build
|
||||
run: |
|
||||
mkdir /tmp/out
|
||||
platform=${{ matrix.platform }}
|
||||
platformPair=${platform//\//-}
|
||||
platformPair=${PLATFORM//\//-}
|
||||
tar -cvzf "/tmp/out/docker-${platformPair}.tar.gz" .
|
||||
if [ -z "${{ matrix.use_glibc }}" ]; then
|
||||
echo "ARTIFACT_NAME=${{ matrix.target }}-${platformPair}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "ARTIFACT_NAME=${{ matrix.target }}-${platformPair}-glibc" >> $GITHUB_ENV
|
||||
fi
|
||||
env:
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ env.ARTIFACT_NAME }}
|
||||
path: /tmp/out/*
|
||||
@@ -101,20 +104,20 @@ jobs:
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_CLIBIN_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_CLIBIN_TOKEN }}
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
|
||||
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
|
||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
||||
with:
|
||||
images: dockereng/cli-bin
|
||||
tags: |
|
||||
@@ -125,7 +128,7 @@ jobs:
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
-
|
||||
name: Build and push image
|
||||
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
|
||||
uses: docker/bake-action@6614cfa25eff9a0b2b2697efb0b6159e7680d584 # v7.2.0
|
||||
with:
|
||||
files: |
|
||||
./docker-bake.hcl
|
||||
@@ -143,7 +146,9 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
-
|
||||
name: Create matrix
|
||||
id: platforms
|
||||
@@ -165,10 +170,10 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
|
||||
uses: docker/bake-action@6614cfa25eff9a0b2b2697efb0b6159e7680d584 # v7.2.0
|
||||
with:
|
||||
targets: plugins-cross
|
||||
set: |
|
||||
|
||||
@@ -46,9 +46,10 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
persist-credentials: false
|
||||
# CodeQL 2.16.4's auto-build added support for multi-module repositories,
|
||||
# and is trying to be smart by searching for modules in every directory,
|
||||
# including vendor directories. If no module is found, it's creating one
|
||||
@@ -61,20 +62,20 @@ jobs:
|
||||
ln -s vendor.sum go.sum
|
||||
-
|
||||
name: Update Go
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||
with:
|
||||
go-version: "1.26.3"
|
||||
go-version: "1.26.4"
|
||||
cache: false
|
||||
-
|
||||
name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
with:
|
||||
languages: go
|
||||
-
|
||||
name: Autobuild
|
||||
uses: github/codeql-action/autobuild@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
-
|
||||
name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
with:
|
||||
category: "/language:go"
|
||||
|
||||
@@ -44,7 +44,9 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
-
|
||||
name: Update daemon.json
|
||||
run: |
|
||||
@@ -63,7 +65,7 @@ jobs:
|
||||
docker info
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
-
|
||||
name: Run ${{ matrix.target }}
|
||||
run: |
|
||||
@@ -74,7 +76,7 @@ jobs:
|
||||
TESTFLAGS: -coverprofile=/tmp/coverage/coverage.txt
|
||||
-
|
||||
name: Send to Codecov
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
|
||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
with:
|
||||
files: ./build/coverage/coverage.txt
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
name: PR Review - Trigger
|
||||
on:
|
||||
pull_request:
|
||||
types: [ready_for_review, opened, review_requested]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
save-context:
|
||||
if: >
|
||||
github.event.comment.user.login != 'docker-agent' &&
|
||||
github.event.comment.user.login != 'docker-agent[bot]' &&
|
||||
github.event.comment.user.type != 'Bot' &&
|
||||
!contains(github.event.comment.body, '<!-- cagent-review -->') &&
|
||||
!contains(github.event.comment.body, '<!-- cagent-review-reply -->')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Save event context
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
COMMENT_JSON: ${{ toJSON(github.event.comment) }}
|
||||
run: |
|
||||
mkdir -p context
|
||||
printf '%s' "${{ github.event_name }}" > context/event_name.txt
|
||||
printf '%s' "$PR_NUMBER" > context/pr_number.txt
|
||||
printf '%s' "$PR_HEAD_SHA" > context/pr_head_sha.txt
|
||||
if [ "${{ github.event_name }}" = "pull_request_review_comment" ]; then
|
||||
printf '%s' "$COMMENT_JSON" > context/comment.json
|
||||
fi
|
||||
|
||||
- name: Upload context
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: pr-review-context
|
||||
path: context/
|
||||
retention-days: 1
|
||||
@@ -0,0 +1,31 @@
|
||||
name: PR Review
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
workflow_run:
|
||||
workflows: ["PR Review - Trigger"]
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
review:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' &&
|
||||
github.event.comment.user.login != 'docker-agent' &&
|
||||
github.event.comment.user.login != 'docker-agent[bot]' &&
|
||||
github.event.comment.user.type != 'Bot' &&
|
||||
!contains(github.event.comment.body, '<!-- cagent-review -->') &&
|
||||
!contains(github.event.comment.body, '<!-- cagent-review-reply -->')) ||
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
uses: docker/cagent-action/.github/workflows/review-pr.yml@3f5dc9969f307d3c76acb7e9ccaefdd96bd62f4b # v1.5.4
|
||||
permissions:
|
||||
contents: read # Read repository files and PR diffs
|
||||
pull-requests: write # Post review comments
|
||||
issues: write # Create security incident issues if secrets detected
|
||||
checks: write # (Optional) Show review progress as a check run
|
||||
id-token: write # Required for OIDC authentication to AWS Secrets Manager
|
||||
actions: read # Download artifacts from trigger workflow
|
||||
with:
|
||||
trigger-run-id: ${{ github.event_name == 'workflow_run' && format('{0}', github.event.workflow_run.id) || '' }}
|
||||
@@ -30,15 +30,15 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
-
|
||||
name: Test
|
||||
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
|
||||
uses: docker/bake-action@6614cfa25eff9a0b2b2697efb0b6159e7680d584 # v7.2.0
|
||||
with:
|
||||
targets: test-coverage
|
||||
-
|
||||
name: Send to Codecov
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
|
||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
with:
|
||||
files: ./build/coverage/coverage.txt
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
@@ -60,14 +60,15 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
path: ${{ env.GOPATH }}/src/github.com/docker/cli
|
||||
persist-credentials: false
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||
with:
|
||||
go-version: "1.26.3"
|
||||
go-version: "1.26.4"
|
||||
cache: false
|
||||
-
|
||||
name: Test
|
||||
@@ -81,7 +82,7 @@ jobs:
|
||||
shell: bash
|
||||
-
|
||||
name: Send to Codecov
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
|
||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
with:
|
||||
files: /tmp/coverage.txt
|
||||
working-directory: ${{ env.GOPATH }}/src/github.com/docker/cli
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Run
|
||||
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
|
||||
uses: docker/bake-action@6614cfa25eff9a0b2b2697efb0b6159e7680d584 # v7.2.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
@@ -48,7 +48,9 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
-
|
||||
name: Generate
|
||||
shell: 'script --return --quiet --command "bash {0}"'
|
||||
@@ -74,7 +76,9 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
-
|
||||
name: Run
|
||||
shell: 'script --return --quiet --command "bash {0}"'
|
||||
@@ -89,14 +93,15 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
path: src/github.com/docker/cli
|
||||
persist-credentials: false
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||
with:
|
||||
go-version: "1.26.3"
|
||||
go-version: "1.26.4"
|
||||
cache: false
|
||||
-
|
||||
name: Run gocompat check
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
name: zizmor
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'master'
|
||||
- '[0-9]+.[0-9]+'
|
||||
- '[0-9]+.x'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
run:
|
||||
uses: crazy-max/.github/.github/workflows/zizmor.yml@716fd1c51a46c5d93a41d44a94b439c9ee802536 # v1.10.0
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
with:
|
||||
min-severity: medium
|
||||
min-confidence: medium
|
||||
persona: pedantic
|
||||
+1
-1
@@ -5,7 +5,7 @@ run:
|
||||
# which causes it to fallback to go1.17 semantics.
|
||||
#
|
||||
# TODO(thaJeztah): update "usetesting" settings to enable go1.24 features once our minimum version is go1.24
|
||||
go: "1.26.3"
|
||||
go: "1.26.4"
|
||||
|
||||
timeout: 5m
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ Alexander Larsson <alexl@redhat.com> <alexander.larsson@gmail.com>
|
||||
Alexander Morozov <lk4d4math@gmail.com>
|
||||
Alexander Morozov <lk4d4math@gmail.com> <lk4d4@docker.com>
|
||||
Alexandre Beslic <alexandre.beslic@gmail.com> <abronan@docker.com>
|
||||
Alexandre Levavasseur <alexandre+oss@13x.fr>
|
||||
Alexis Couvreur <alexiscouvreur.pro@gmail.com>
|
||||
Alicia Lauerman <alicia@eta.im> <allydevour@me.com>
|
||||
Allen Sun <allensun.shl@alibaba-inc.com> <allen.sun@daocloud.io>
|
||||
@@ -354,9 +355,10 @@ Lorenzo Fontana <lo@linux.com> <fontanalorenzo@me.com>
|
||||
Louis Opter <kalessin@kalessin.fr>
|
||||
Louis Opter <kalessin@kalessin.fr> <louis@dotcloud.com>
|
||||
Lovekesh Kumar <lovekesh.kumar@rtcamp.com>
|
||||
Luo Jiyin <luojiyin@hotmail.com>
|
||||
Luca Favatella <luca.favatella@erlang-solutions.com> <lucafavatella@users.noreply.github.com>
|
||||
Lukas Michael <lukas.23022005@gmail.com>
|
||||
Luke Marsden <me@lukemarsden.net> <luke@digital-crocus.com>
|
||||
Luo Jiyin <luojiyin@hotmail.com>
|
||||
Lyn <energylyn@zju.edu.cn>
|
||||
Lynda O'Leary <lyndaoleary29@gmail.com>
|
||||
Lynda O'Leary <lyndaoleary29@gmail.com> <lyndaoleary@hotmail.com>
|
||||
@@ -403,6 +405,7 @@ Michael Huettermann <michael@huettermann.net>
|
||||
Michael Käufl <docker@c.michael-kaeufl.de> <michael-k@users.noreply.github.com>
|
||||
Michael Spetsiotis <michael_spets@hotmail.com>
|
||||
Michal Minář <miminar@redhat.com>
|
||||
Mickael Emirkanian <mickael.emirkanian@docker.com>
|
||||
Miguel Angel Alvarez Cabrerizo <doncicuto@gmail.com> <30386061+doncicuto@users.noreply.github.com>
|
||||
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
||||
Mihai Borobocea <MihaiBorob@gmail.com> <MihaiBorobocea@gmail.com>
|
||||
@@ -572,6 +575,8 @@ Ulysses Souza <ulysses.souza@docker.com>
|
||||
Ulysses Souza <ulysses.souza@docker.com> <ulyssessouza@gmail.com>
|
||||
Umesh Yadav <umesh4257@gmail.com>
|
||||
Umesh Yadav <umesh4257@gmail.com> <dungeonmaster18@users.noreply.github.com>
|
||||
Varun Hotani <varunhotani@gmail.com>
|
||||
Vibhu Anan <vibhuanand@outlook.com>
|
||||
Victor Lyuboslavsky <victor@victoreda.com>
|
||||
Victor Vieux <victor.vieux@docker.com> <dev@vvieux.com>
|
||||
Victor Vieux <victor.vieux@docker.com> <victor.vieux@dotcloud.com>
|
||||
|
||||
@@ -43,6 +43,7 @@ Alexander Larsson <alexl@redhat.com>
|
||||
Alexander Morozov <lk4d4math@gmail.com>
|
||||
Alexander Ryabov <i@sepa.spb.ru>
|
||||
Alexandre González <agonzalezro@gmail.com>
|
||||
Alexandre Levavasseur <alexandre+oss@13x.fr>
|
||||
Alexandre Vallières-Lagacé <alexandre.valliereslagace@docker.com>
|
||||
Alexey Igrychev <alexey.igrychev@flant.com>
|
||||
Alexis Couvreur <alexiscouvreur.pro@gmail.com>
|
||||
@@ -161,6 +162,7 @@ Chen Chuanliang <chen.chuanliang@zte.com.cn>
|
||||
Chen Hanxiao <chenhanxiao@cn.fujitsu.com>
|
||||
Chen Mingjie <chenmingjie0828@163.com>
|
||||
Chen Qiu <cheney-90@hotmail.com>
|
||||
Ching Wei Kang <164879897+WilliamK112@users.noreply.github.com>
|
||||
Chris Chinchilla <chris@chrischinchilla.com>
|
||||
Chris Couzens <ccouzens@gmail.com>
|
||||
Chris Gavin <chris@chrisgavin.me>
|
||||
@@ -366,7 +368,7 @@ Hugo Gabriel Eyherabide <hugogabriel.eyherabide@gmail.com>
|
||||
huqun <huqun@zju.edu.cn>
|
||||
Huu Nguyen <huu@prismskylabs.com>
|
||||
Hyzhou Zhy <hyzhou.zhy@alibaba-inc.com>
|
||||
Iain MacDonald <ijmacd@gmail.com>
|
||||
Iain MacDonald <IJMacD@gmail.com>
|
||||
Iain Samuel McLean Elder <iain@isme.es>
|
||||
Ian Campbell <ian.campbell@docker.com>
|
||||
Ian Philpot <ian.philpot@microsoft.com>
|
||||
@@ -552,6 +554,7 @@ Ludovic Temgoua Abanda <abandaludovic500@gmail.com>
|
||||
Luis Henrique Mulinari <luis.mulinari@gmail.com>
|
||||
Luka Hartwig <mail@lukahartwig.de>
|
||||
Lukas Heeren <lukas-heeren@hotmail.com>
|
||||
Lukas Michael <lukas.23022005@gmail.com>
|
||||
Lukasz Zajaczkowski <Lukasz.Zajaczkowski@ts.fujitsu.com>
|
||||
Luo Jiyin <luojiyin@hotmail.com>
|
||||
Lydell Manganti <LydellManganti@users.noreply.github.com>
|
||||
@@ -562,6 +565,7 @@ Maciej Kalisz <maciej.d.kalisz@gmail.com>
|
||||
Madhav Puri <madhav.puri@gmail.com>
|
||||
Madhu Venugopal <madhu@socketplane.io>
|
||||
Madhur Batra <madhurbatra097@gmail.com>
|
||||
Mahesh Thakur <maheshthakur9152@gmail.com>
|
||||
Malte Janduda <mail@janduda.net>
|
||||
Manjunath A Kumatagi <mkumatag@in.ibm.com>
|
||||
Mansi Nahar <mmn4185@rit.edu>
|
||||
@@ -589,10 +593,12 @@ Mathieu Rollet <matletix@gmail.com>
|
||||
Matt Gucci <matt9ucci@gmail.com>
|
||||
Matt Robenolt <matt@ydekproductions.com>
|
||||
Matteo Orefice <matteo.orefice@bites4bits.software>
|
||||
Matteo Panzeri <matteo1782@gmail.com>
|
||||
Matthew Heon <mheon@redhat.com>
|
||||
Matthieu Hauglustaine <matt.hauglustaine@gmail.com>
|
||||
Matthieu MOREL <matthieu.morel35@gmail.com>
|
||||
Mauro Porras P <mauroporrasp@gmail.com>
|
||||
Max Morozov <gtmax.yo@gmail.com>
|
||||
Max Shytikov <mshytikov@gmail.com>
|
||||
Max-Julian Pogner <max-julian@pogner.at>
|
||||
Maxime Petazzoni <max@signalfuse.com>
|
||||
@@ -617,6 +623,7 @@ Michael West <mwest@mdsol.com>
|
||||
Michael Zampani <michael.zampani@docker.com>
|
||||
Michal Minář <miminar@redhat.com>
|
||||
Michał Czeraszkiewicz <czerasz@gmail.com>
|
||||
Mickael Emirkanian <mickael.emirkanian@docker.com>
|
||||
Miguel Angel Alvarez Cabrerizo <doncicuto@gmail.com>
|
||||
Mihai Borobocea <MihaiBorob@gmail.com>
|
||||
Mihuleacc Sergiu <mihuleac.sergiu@gmail.com>
|
||||
@@ -638,6 +645,7 @@ Mohammad Banikazemi <mb@us.ibm.com>
|
||||
Mohammad Hossein <mhm98035@gmail.com>
|
||||
Mohammed Aaqib Ansari <maaquib@gmail.com>
|
||||
Mohammed Aminu Futa <mohammedfuta2000@gmail.com>
|
||||
Mohammed Thaha <mohammedthahacse@gmail.com>
|
||||
Mohini Anne Dsouza <mohini3917@gmail.com>
|
||||
Moorthy RS <rsmoorthy@gmail.com>
|
||||
Morgan Bauer <mbauer@us.ibm.com>
|
||||
@@ -689,6 +697,7 @@ Olli Janatuinen <olli.janatuinen@gmail.com>
|
||||
Oscar Wieman <oscrx@icloud.com>
|
||||
Otto Kekäläinen <otto@seravo.fi>
|
||||
Ovidio Mallo <ovidio.mallo@gmail.com>
|
||||
Park Jaeon <me@finalchild.dev>
|
||||
Pascal Borreli <pascal@borreli.com>
|
||||
Patrick Böänziger <patrick.baenziger@bsi-software.com>
|
||||
Patrick Daigle <114765035+pdaig@users.noreply.github.com>
|
||||
@@ -715,6 +724,7 @@ Peter Jaffe <pjaffe@nevo.com>
|
||||
Peter Kehl <peter.kehl@gmail.com>
|
||||
Peter Nagy <xificurC@gmail.com>
|
||||
Peter Salvatore <peter@psftw.com>
|
||||
Peter Valdemar Mørch <peter@morch.com>
|
||||
Peter Waller <p@pwaller.net>
|
||||
Phil Estes <estesp@gmail.com>
|
||||
Philip Alexander Etling <paetling@gmail.com>
|
||||
@@ -739,6 +749,7 @@ Ray Tsang <rayt@google.com>
|
||||
Reficul <xuzhenglun@gmail.com>
|
||||
Remy Suen <remy.suen@gmail.com>
|
||||
Renaud Gaubert <rgaubert@nvidia.com>
|
||||
René Hermenau <rene-hermenau@users.noreply.github.com>
|
||||
Ricardo N Feliciano <FelicianoTech@gmail.com>
|
||||
Rich Moyse <rich@moyse.us>
|
||||
Richard Chen Zheng <58443436+rchenzheng@users.noreply.github.com>
|
||||
@@ -789,6 +800,7 @@ Scott Collier <emailscottcollier@gmail.com>
|
||||
Sean Christopherson <sean.j.christopherson@intel.com>
|
||||
Sean Rodman <srodman7689@gmail.com>
|
||||
Sebastiaan van Stijn <github@gone.nl>
|
||||
Seiya Miyata <odradek38@gmail.com>
|
||||
Sergey Tryuber <Sergeant007@users.noreply.github.com>
|
||||
Serhat Gülçiçek <serhat25@gmail.com>
|
||||
Sevki Hasirci <s@sevki.org>
|
||||
@@ -889,8 +901,10 @@ Umesh Yadav <umesh4257@gmail.com>
|
||||
Vaclav Struhar <struharv@gmail.com>
|
||||
Valentin Lorentz <progval+git@progval.net>
|
||||
Vardan Pogosian <vardan.pogosyan@gmail.com>
|
||||
Varun Hotani <varunhotani@gmail.com>
|
||||
Venkateswara Reddy Bukkasamudram <bukkasamudram@outlook.com>
|
||||
Veres Lajos <vlajos@gmail.com>
|
||||
Vibhu Anan <vibhuanand@outlook.com>
|
||||
Victor Vieux <victor.vieux@docker.com>
|
||||
Victoria Bialas <victoria.bialas@docker.com>
|
||||
Viktor Stanchev <me@viktorstanchev.com>
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ ARG BASE_VARIANT=alpine
|
||||
ARG ALPINE_VERSION=3.23
|
||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||
|
||||
ARG GO_VERSION=1.26.3
|
||||
ARG GO_VERSION=1.26.4
|
||||
|
||||
# XX_VERSION specifies the version of the xx utility to use.
|
||||
# It must be a valid tag in the docker.io/tonistiigi/xx image repository.
|
||||
|
||||
@@ -123,6 +123,7 @@ To push the complete multi-platform image, remove the --platform flag.
|
||||
return err
|
||||
}
|
||||
|
||||
var notes []string
|
||||
defer func() {
|
||||
_ = responseBody.Close()
|
||||
for _, note := range notes {
|
||||
@@ -131,18 +132,16 @@ To push the complete multi-platform image, remove the --platform flag.
|
||||
}()
|
||||
|
||||
if opts.quiet {
|
||||
err = jsonstream.Display(ctx, responseBody, streams.NewOut(io.Discard), jsonstream.WithAuxCallback(handleAux()))
|
||||
err = jsonstream.Display(ctx, responseBody, streams.NewOut(io.Discard), jsonstream.WithAuxCallback(handleAux(¬es, out)))
|
||||
if err == nil {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), ref.String())
|
||||
}
|
||||
return err
|
||||
}
|
||||
return jsonstream.Display(ctx, responseBody, dockerCli.Out(), jsonstream.WithAuxCallback(handleAux()))
|
||||
return jsonstream.Display(ctx, responseBody, dockerCli.Out(), jsonstream.WithAuxCallback(handleAux(¬es, out)))
|
||||
}
|
||||
|
||||
var notes []string
|
||||
|
||||
func handleAux() func(jm jsonstream.JSONMessage) {
|
||||
func handleAux(notes *[]string, out tui.Output) func(jm jsonstream.JSONMessage) {
|
||||
return func(jm jsonstream.JSONMessage) {
|
||||
b := []byte(*jm.Aux)
|
||||
|
||||
@@ -150,10 +149,10 @@ func handleAux() func(jm jsonstream.JSONMessage) {
|
||||
err := json.Unmarshal(b, &stripped)
|
||||
if err == nil && stripped.ManifestPushedInsteadOfIndex {
|
||||
note := fmt.Sprintf("Not all multiplatform-content is present and only the available single-platform image was pushed\n%s -> %s",
|
||||
aec.RedF.Apply(stripped.OriginalIndex.Digest.String()),
|
||||
aec.GreenF.Apply(stripped.SelectedManifest.Digest.String()),
|
||||
out.Color(aec.RedF).Apply(stripped.OriginalIndex.Digest.String()),
|
||||
out.Color(aec.GreenF).Apply(stripped.SelectedManifest.Digest.String()),
|
||||
)
|
||||
notes = append(notes, note)
|
||||
*notes = append(*notes, note)
|
||||
}
|
||||
|
||||
var missing auxprogress.ContentMissing
|
||||
@@ -166,7 +165,7 @@ func handleAux() func(jm jsonstream.JSONMessage) {
|
||||
Make sure you have all the referenced content and try again.
|
||||
|
||||
You can also push only a single platform specific manifest directly by specifying the platform you want to push with the --platform flag.`
|
||||
notes = append(notes, note)
|
||||
*notes = append(*notes, note)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/moby/moby/api/types/auxprogress"
|
||||
"github.com/moby/moby/client"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
@@ -86,3 +91,28 @@ func TestNewPushCommandSuccess(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunPushRespectsNoColorForAuxNotes(t *testing.T) {
|
||||
t.Setenv("NO_COLOR", "1")
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
imagePushFunc: func(ref string, options client.ImagePushOptions) (client.ImagePushResponse, error) {
|
||||
aux, err := json.Marshal(auxprogress.ManifestPushedInsteadOfIndex{
|
||||
ManifestPushedInsteadOfIndex: true,
|
||||
OriginalIndex: ocispec.Descriptor{Digest: "sha256:1111111111111111111111111111111111111111111111111111111111111111"},
|
||||
SelectedManifest: ocispec.Descriptor{Digest: "sha256:2222222222222222222222222222222222222222222222222222222222222222"},
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
line := append([]byte(`{"aux":`), aux...)
|
||||
line = append(line, '}', '\n')
|
||||
return fakeStreamResult{ReadCloser: io.NopCloser(bytes.NewReader(line))}, nil
|
||||
},
|
||||
})
|
||||
cli.Out().SetIsTerminal(true)
|
||||
|
||||
err := runPush(t.Context(), cli, pushOptions{remote: "image:tag"})
|
||||
assert.NilError(t, err)
|
||||
|
||||
out := cli.OutBuffer().String()
|
||||
assert.Assert(t, strings.Contains(out, "sha256:1111111111111111111111111111111111111111111111111111111111111111 -> sha256:2222222222222222222222222222222222222222222222222222222222222222"))
|
||||
assert.Assert(t, !strings.Contains(out, "\x1b["), "output should not contain ANSI escape codes, output: %s", out)
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func newLoginCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.StringVarP(&opts.user, "username", "u", "", "Username")
|
||||
flags.StringVarP(&opts.password, "password", "p", "", "Password or Personal Access Token (PAT)")
|
||||
flags.StringVarP(&opts.password, "password", "p", "", `Password or Personal Access Token (PAT), or "-" to read from stdin`)
|
||||
flags.BoolVar(&opts.passwordStdin, "password-stdin", false, "Take the Password or Personal Access Token (PAT) from stdin")
|
||||
|
||||
return cmd
|
||||
@@ -72,8 +72,8 @@ func newLoginCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
//
|
||||
// TODO(thaJeztah); combine with verifyLoginOptions, but this requires rewrites of many tests.
|
||||
func verifyLoginFlags(flags *pflag.FlagSet, opts loginOptions) error {
|
||||
if flags.Changed("password-stdin") {
|
||||
if flags.Changed("password") {
|
||||
if flags.Changed("password-stdin") || opts.password == "-" {
|
||||
if flags.Changed("password") && opts.password != "-" {
|
||||
return errors.New("conflicting options: cannot specify both --password and --password-stdin")
|
||||
}
|
||||
if !flags.Changed("username") {
|
||||
@@ -122,6 +122,11 @@ func readSecretFromStdin(r io.Reader) (string, error) {
|
||||
}
|
||||
|
||||
func verifyLoginOptions(dockerCLI command.Streams, opts *loginOptions) error {
|
||||
if opts.password == "-" {
|
||||
opts.password = ""
|
||||
opts.passwordStdin = true
|
||||
}
|
||||
|
||||
if opts.password != "" {
|
||||
_, _ = fmt.Fprintln(dockerCLI.Err(), "WARNING! Using --password via the CLI is insecure. Use --password-stdin.")
|
||||
}
|
||||
|
||||
@@ -339,6 +339,57 @@ func TestRunLogin(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
doc: "password dash reads password from stdin",
|
||||
priorCredentials: map[string]configtypes.AuthConfig{},
|
||||
stdIn: "my password\r\n",
|
||||
input: loginOptions{
|
||||
serverAddress: "reg1",
|
||||
user: "my-username",
|
||||
password: "-",
|
||||
},
|
||||
expectedCredentials: map[string]configtypes.AuthConfig{
|
||||
"reg1": {
|
||||
Username: "my-username",
|
||||
Password: "my password",
|
||||
ServerAddress: "reg1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
doc: "password dash empty stdin",
|
||||
priorCredentials: map[string]configtypes.AuthConfig{},
|
||||
input: loginOptions{
|
||||
serverAddress: "reg1",
|
||||
user: "my-username",
|
||||
password: "-",
|
||||
},
|
||||
expectedErr: `password is empty`,
|
||||
expectedCredentials: map[string]configtypes.AuthConfig{
|
||||
"reg1": {
|
||||
Username: "my-username",
|
||||
ServerAddress: "reg1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
doc: "password dash and password stdin read password from stdin",
|
||||
priorCredentials: map[string]configtypes.AuthConfig{},
|
||||
stdIn: "my password\r\n",
|
||||
input: loginOptions{
|
||||
serverAddress: "reg1",
|
||||
user: "my-username",
|
||||
password: "-",
|
||||
passwordStdin: true,
|
||||
},
|
||||
expectedCredentials: map[string]configtypes.AuthConfig{
|
||||
"reg1": {
|
||||
Username: "my-username",
|
||||
Password: "my password",
|
||||
ServerAddress: "reg1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
doc: "password with leading and trailing spaces",
|
||||
priorCredentials: map[string]configtypes.AuthConfig{},
|
||||
@@ -397,7 +448,7 @@ func TestRunLogin(t *testing.T) {
|
||||
cfg := configfile.New(filepath.Join(tmpDir, "config.json"))
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cli.SetConfigFile(cfg)
|
||||
if tc.input.passwordStdin {
|
||||
if tc.input.passwordStdin || tc.input.password == "-" || tc.stdIn != "" {
|
||||
if tc.expectedErr == "TEST_READ_ERR" {
|
||||
cli.SetIn(streams.NewIn(io.NopCloser(iotest.ErrReader(errors.New(tc.expectedErr)))))
|
||||
} else {
|
||||
@@ -632,6 +683,21 @@ func TestLoginValidateFlags(t *testing.T) {
|
||||
args: []string{"--password-stdin", "--password", ""},
|
||||
expectedErr: `conflicting options: cannot specify both --password and --password-stdin`,
|
||||
},
|
||||
{
|
||||
name: "password stdin and password dash without stdin",
|
||||
args: []string{"--password-stdin", "--username", "my-username", "--password", "-"},
|
||||
expectedErr: `password is empty`,
|
||||
},
|
||||
{
|
||||
name: "password dash without username",
|
||||
args: []string{"--password", "-"},
|
||||
expectedErr: `the --password-stdin option requires --username to be set`,
|
||||
},
|
||||
{
|
||||
name: "short password dash without username",
|
||||
args: []string{"-p", "-"},
|
||||
expectedErr: `the --password-stdin option requires --username to be set`,
|
||||
},
|
||||
{
|
||||
name: "empty --password",
|
||||
args: []string{"--password", ""},
|
||||
@@ -658,3 +724,10 @@ func TestLoginValidateFlags(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginHelpDocumentsPasswordDash(t *testing.T) {
|
||||
cmd := newLoginCommand(test.NewFakeCli(&fakeClient{}))
|
||||
flag := cmd.Flags().Lookup("password")
|
||||
assert.Check(t, flag != nil)
|
||||
assert.Check(t, is.Contains(flag.Usage, `"-"`))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.26.3
|
||||
ARG GO_VERSION=1.26.4
|
||||
|
||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.26.3
|
||||
ARG GO_VERSION=1.26.4
|
||||
|
||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.26.3
|
||||
ARG GO_VERSION=1.26.4
|
||||
|
||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||
|
||||
@@ -113,6 +113,7 @@ The following table provides an overview of the current status of deprecated fea
|
||||
| Deprecated | [`-h` shorthand for `--help`](#-h-shorthand-for---help) | v1.12 | v17.09 |
|
||||
| Removed | [`-e` and `--email` flags on `docker login`](#-e-and---email-flags-on-docker-login) | v1.11 | v17.06 |
|
||||
| Deprecated | [Separator (`:`) of `--security-opt` flag on `docker run`](#separator--of---security-opt-flag-on-docker-run) | v1.11 | v17.06 |
|
||||
| Deprecated | [Links on the default bridge network](#links-on-the-default-bridge-network) | v1.10 | - |
|
||||
| Deprecated | [Ambiguous event fields in API](#ambiguous-event-fields-in-api) | v1.10 | - |
|
||||
| Removed | [`-f` flag on `docker tag`](#-f-flag-on-docker-tag) | v1.10 | v1.12 |
|
||||
| Removed | [HostConfig at API container start](#hostconfig-at-api-container-start) | v1.10 | v1.12 |
|
||||
@@ -1203,6 +1204,19 @@ The `docker login` no longer automatically registers an account with the target
|
||||
|
||||
The flag `--security-opt` doesn't use the colon separator (`:`) anymore to divide keys and values, it uses the equal symbol (`=`) for consistency with other similar flags, like `--storage-opt`.
|
||||
|
||||
### Links on the default bridge network
|
||||
|
||||
**Deprecated in release: v1.10**
|
||||
**Target for removal in release: v30.0**
|
||||
|
||||
The `--link` option on `docker create` and `docker run`, when used with no
|
||||
`--network` specified, was deprecated in v1.10 and will be removed in a future
|
||||
release. Custom networks should be used instead. Docker 29.6 added a deprecation
|
||||
warning when this option is used for the default bridge network.
|
||||
|
||||
Note that the `--link` option is still supported when a non-default network
|
||||
is used.
|
||||
|
||||
### Ambiguous event fields in API
|
||||
|
||||
**Deprecated in release: [v1.10.0](https://github.com/docker/docker/releases/tag/v1.10.0)**
|
||||
|
||||
@@ -74,11 +74,14 @@ The sequence diagrams below depict an allow and deny authorization flow:
|
||||
Each request sent to the plugin includes the authenticated user, the HTTP
|
||||
headers, and the request/response body. Only the user name and the
|
||||
authentication method used are passed to the plugin. Most importantly, no user
|
||||
credentials or tokens are passed. Finally, not all request/response bodies
|
||||
are sent to the authorization plugin. Only request/response bodies where
|
||||
the `Content-Type` is `application/json` are sent to the authorization plugin;
|
||||
bodies of any other `Content-Type` are not visible to the plugin and cannot
|
||||
be used for enforcement, even though the daemon may still act on this data.
|
||||
credentials or tokens are passed.
|
||||
|
||||
> [!NOTE]
|
||||
> Authorization plugins enforce requests to the Docker daemon's HTTP API only. gRPC method
|
||||
> calls, whether dispatched natively or upgraded through `POST /grpc`, are not subject to authorization.
|
||||
> Furthermore, HTTP request/response bodies where the `Content-Type` is `application/json` are forwarded;
|
||||
> bodies of any other type are not visible to the plugin and cannot be used for enforcement,
|
||||
> even though the daemon acts on this data.
|
||||
|
||||
For commands that can potentially hijack the HTTP connection (`HTTP
|
||||
Upgrade`), such as `exec`, the authorization plugin is only called for the
|
||||
@@ -88,6 +91,10 @@ passed to the authorization plugins. For commands that return chunked HTTP
|
||||
response, such as `logs` and `events`, only the HTTP request is sent to the
|
||||
authorization plugins.
|
||||
|
||||
The Engine's authorization middleware fails closed: when a plugin returns an error or returns `Allow: false`,
|
||||
the request is denied and the error is surfaced to the client. Plugins should also fail closed: if the plugin
|
||||
cannot confidently evaluate a request, it should return an error or `Allow: false`.
|
||||
|
||||
### Response body size and partial buffering
|
||||
|
||||
The internal buffer that holds the response body between the daemon's HTTP
|
||||
@@ -236,7 +243,7 @@ Name | Type | Description
|
||||
User | string | The user identification
|
||||
Authentication method | string | The authentication method used
|
||||
Request method | enum | The HTTP method (GET/DELETE/POST)
|
||||
Request URI | string | The HTTP request URI including API version (e.g., v.1.17/containers/json)
|
||||
Request URI | string | The HTTP request URI including API version, as sent by the client (e.g., v.1.17/containers/json)
|
||||
Request headers | map[string]string | Request headers as key value pairs (without the authorization header)
|
||||
Request body | []byte | Raw request body
|
||||
|
||||
@@ -259,7 +266,7 @@ Name | Type | Description
|
||||
User | string | The user identification
|
||||
Authentication method | string | The authentication method used
|
||||
Request method | string | The HTTP method (GET/DELETE/POST)
|
||||
Request URI | string | The HTTP request URI including API version (e.g., v.1.17/containers/json)
|
||||
Request URI | string | The HTTP request URI including API version, as sent by the client (e.g., v.1.17/containers/json)
|
||||
Request headers | map[string]string | Request headers as key value pairs (without the authorization header)
|
||||
Request body | []byte | Raw request body
|
||||
Response status code | int | Status code from the Docker daemon
|
||||
|
||||
@@ -9,14 +9,14 @@ Fetch the logs of a container
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------|:---------|:--------|:---------------------------------------------------------------------------------------------------|
|
||||
| `--details` | `bool` | | Show extra details provided to logs |
|
||||
| `-f`, `--follow` | `bool` | | Follow log output |
|
||||
| `--since` | `string` | | Show logs since timestamp (e.g. `2013-01-02T13:23:37Z`) or relative (e.g. `42m` for 42 minutes) |
|
||||
| `-n`, `--tail` | `string` | `all` | Number of lines to show from the end of the logs |
|
||||
| `-t`, `--timestamps` | `bool` | | Show timestamps |
|
||||
| [`--until`](#until) | `string` | | Show logs before a timestamp (e.g. `2013-01-02T13:23:37Z`) or relative (e.g. `42m` for 42 minutes) |
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------------------------------------|:---------|:--------|:---------------------------------------------------------------------------------------------------|
|
||||
| [`--details`](#details) | `bool` | | Show extra details provided to logs |
|
||||
| [`-f`](#follow), [`--follow`](#follow) | `bool` | | Follow log output |
|
||||
| [`--since`](#since) | `string` | | Show logs since timestamp (e.g. `2013-01-02T13:23:37Z`) or relative (e.g. `42m` for 42 minutes) |
|
||||
| [`-n`](#tail), [`--tail`](#tail) | `string` | `all` | Number of lines to show from the end of the logs |
|
||||
| [`-t`](#timestamps), [`--timestamps`](#timestamps) | `bool` | | Show timestamps |
|
||||
| [`--until`](#until) | `string` | | Show logs before a timestamp (e.g. `2013-01-02T13:23:37Z`) or relative (e.g. `42m` for 42 minutes) |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
@@ -28,21 +28,34 @@ The `docker logs` command batch-retrieves logs present at the time of execution.
|
||||
For more information about selecting and configuring logging drivers, refer to
|
||||
[Configure logging drivers](https://docs.docker.com/engine/logging/configure/).
|
||||
|
||||
## Examples
|
||||
|
||||
### <a name="follow"></a> Stream log output (-f, --follow)
|
||||
|
||||
The `docker logs --follow` command will continue streaming the new output from
|
||||
the container's `STDOUT` and `STDERR`.
|
||||
|
||||
### <a name="tail"></a> Retrieve the last logs (-n, --tail)
|
||||
|
||||
Passing a negative number or a non-integer to `--tail` is invalid and the
|
||||
value is set to `all` in that case.
|
||||
|
||||
|
||||
### <a name="timestamps"></a> Retrieve logs with timestamps (-t, --timestamps)
|
||||
|
||||
The `docker logs --timestamps` command will add an [RFC3339Nano timestamp](https://pkg.go.dev/time#RFC3339Nano)
|
||||
, for example `2014-09-16T06:17:46.000000000Z`, to each
|
||||
log entry. To ensure that the timestamps are aligned the
|
||||
nano-second part of the timestamp will be padded with zero when necessary.
|
||||
|
||||
### <a name="details"></a> Retrieve logs with additional attributes (--details)
|
||||
|
||||
The `docker logs --details` command will add on extra attributes, such as
|
||||
environment variables and labels, provided to `--log-opt` when creating the
|
||||
container.
|
||||
|
||||
### <a name="since"></a> Retrieve logs generated since a specific point in time (--since)
|
||||
|
||||
The `--since` option shows only the container logs generated after
|
||||
a given date. You can specify the date as an RFC 3339 date, a UNIX
|
||||
timestamp, or a Go duration string (e.g. `1m30s`, `3h`). Besides RFC3339 date
|
||||
@@ -56,7 +69,6 @@ seconds (aka Unix epoch or Unix time), and the optional .nanoseconds field is a
|
||||
fraction of a second no more than nine digits long. You can combine the
|
||||
`--since` option with either or both of the `--follow` or `--tail` options.
|
||||
|
||||
## Examples
|
||||
|
||||
### <a name="until"></a> Retrieve logs until a specific point in time (--until)
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ Defaults to Docker Hub if no server is specified.
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------------------------------|:---------|:--------|:------------------------------------------------------------|
|
||||
| `-p`, `--password` | `string` | | Password or Personal Access Token (PAT) |
|
||||
| [`--password-stdin`](#password-stdin) | `bool` | | Take the Password or Personal Access Token (PAT) from stdin |
|
||||
| [`-u`](#username), [`--username`](#username) | `string` | | Username |
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------------------------------|:---------|:--------|:-------------------------------------------------------------------|
|
||||
| `-p`, `--password` | `string` | | Password or Personal Access Token (PAT), or `-` to read from stdin |
|
||||
| [`--password-stdin`](#password-stdin) | `bool` | | Take the Password or Personal Access Token (PAT) from stdin |
|
||||
| [`-u`](#username), [`--username`](#username) | `string` | | Username |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
@@ -244,6 +244,13 @@ The following example reads a password from a file, and passes it to the
|
||||
$ cat ~/my_password.txt | docker login --username foo --password-stdin
|
||||
```
|
||||
|
||||
You can also pass `-` as the value for `--password` or `-p` to read the
|
||||
password from `STDIN`.
|
||||
|
||||
```console
|
||||
$ cat ~/my_password.txt | docker login --username foo --password -
|
||||
```
|
||||
|
||||
## Related commands
|
||||
|
||||
* [logout](logout.md)
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
//go:build tools
|
||||
|
||||
package main
|
||||
|
||||
import _ "github.com/cpuguy83/go-md2man/v2"
|
||||
@@ -98,6 +98,16 @@ fi
|
||||
if [ "$CGO_ENABLED" = "1" ] && [ "$GO_LINKMODE" = "static" ] && [ "$(go env GOOS)" = "linux" ]; then
|
||||
GO_LDFLAGS="$GO_LDFLAGS -linkmode external -extldflags -static"
|
||||
fi
|
||||
|
||||
# We only import google.golang.org/grpc as an indirect dependency, and
|
||||
# do not make gRPC connections.
|
||||
#
|
||||
# grpcnotrace avoids importing golang.org/x/net/trace, which in turn enables
|
||||
# dead code elimination, which can reduce binary size when tracing is not needed.
|
||||
#
|
||||
# see https://github.com/grpc/grpc-go/blob/v1.81.1/trace_notrace.go#L23-L25
|
||||
GO_BUILDTAGS="$GO_BUILDTAGS grpcnotrace"
|
||||
|
||||
if [ "$CGO_ENABLED" = "1" ] && [ "$GO_LINKMODE" = "static" ]; then
|
||||
# compiling statically with CGO enabled requires osusergo and netgo to be set.
|
||||
GO_BUILDTAGS="$GO_BUILDTAGS osusergo netgo"
|
||||
|
||||
@@ -7,15 +7,16 @@ set -eu
|
||||
if ! command -v "$GO_MD2MAN" > /dev/null; then
|
||||
(
|
||||
set -x
|
||||
go build -mod=vendor -modfile=vendor.mod -o ./build/tools/go-md2man ./vendor/github.com/cpuguy83/go-md2man/v2
|
||||
# note: this installs all tools defined in go.mod/vendor.mod
|
||||
GOBIN="$(pwd)/build/tools" go install -mod=vendor tool
|
||||
)
|
||||
GO_MD2MAN=$(realpath ./build/tools/go-md2man)
|
||||
GO_MD2MAN="$(pwd)/build/tools/go-md2man"
|
||||
fi
|
||||
|
||||
mkdir -p man/man1
|
||||
(
|
||||
set -x
|
||||
go run -mod=vendor -modfile=vendor.mod -tags manpages ./man/generate.go --source "./man/src" --target "./man/man1"
|
||||
go run -mod=vendor -tags manpages ./man/generate.go --source "./man/src" --target "./man/man1"
|
||||
)
|
||||
|
||||
(
|
||||
|
||||
@@ -4,7 +4,7 @@ set -eu
|
||||
|
||||
(
|
||||
set -x
|
||||
go run -mod=vendor -modfile=vendor.mod -tags docsgen ./docs/generate/generate.go --formats md --source "./docs/reference/commandline" --target "./docs/reference/commandline"
|
||||
go run -mod=vendor -tags docsgen ./docs/generate/generate.go --formats md --source "./docs/reference/commandline" --target "./docs/reference/commandline"
|
||||
)
|
||||
|
||||
# remove generated help.md file
|
||||
|
||||
@@ -4,4 +4,4 @@ set -eu
|
||||
|
||||
mkdir -p docs/yaml
|
||||
set -x
|
||||
go run -mod=vendor -modfile=vendor.mod -tags docsgen ./docs/generate/generate.go --formats yaml --source "./docs/reference/commandline" --target "./docs/yaml"
|
||||
go run -mod=vendor -tags docsgen ./docs/generate/generate.go --formats yaml --source "./docs/reference/commandline" --target "./docs/yaml"
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ if [ -z "$TYP" ]; then
|
||||
fi
|
||||
|
||||
update() {
|
||||
(set -x ; go mod tidy -modfile=vendor.mod; go mod vendor -modfile=vendor.mod)
|
||||
(set -x ; go mod tidy; go mod vendor)
|
||||
}
|
||||
|
||||
validate() {
|
||||
@@ -31,7 +31,7 @@ outdated() {
|
||||
echo "go-mod-outdated not found. Install with 'go install github.com/psampaz/go-mod-outdated@v0.8.0'"
|
||||
exit 1
|
||||
fi
|
||||
(set -x ; go list -mod=vendor -mod=readonly -modfile=vendor.mod -u -m -json all | go-mod-outdated -update -direct)
|
||||
(set -x ; go list -mod=readonly -u -m -json all | go-mod-outdated -update -direct)
|
||||
}
|
||||
|
||||
case $TYP in
|
||||
|
||||
+23
-15
@@ -6,28 +6,36 @@
|
||||
# when the command is finished. This script should be dropped when this
|
||||
# repository is a proper Go module with a permanent go.mod.
|
||||
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOTDIR="$(cd "${SCRIPTDIR}/.." && pwd)"
|
||||
|
||||
if test -e "${ROOTDIR}/go.mod"; then
|
||||
{
|
||||
scriptname=$(basename "$0")
|
||||
cat >&2 <<- EOF
|
||||
$scriptname: WARN: go.mod exists in the repository root!
|
||||
$scriptname: WARN: Using your go.mod instead of our generated version -- this may misbehave!
|
||||
EOF
|
||||
} >&2
|
||||
else
|
||||
cleanup_paths=()
|
||||
|
||||
create_symlink() {
|
||||
local target="$1"
|
||||
local link="$2"
|
||||
|
||||
if [ -e "$link" ]; then
|
||||
# see https://superuser.com/a/196698
|
||||
if ! [ "$link" -ef "${ROOTDIR}/${target}" ]; then
|
||||
echo "$(basename "$0"): WARN: $link exists but is not the expected symlink!" >&2
|
||||
echo "$(basename "$0"): WARN: Using your version instead of our generated version -- this may misbehave!" >&2
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
set -x
|
||||
ln -s "$target" "$link"
|
||||
cleanup_paths+=( "$link" )
|
||||
}
|
||||
|
||||
tee "${ROOTDIR}/go.mod" >&2 <<- EOF
|
||||
module github.com/docker/cli
|
||||
create_symlink "vendor.mod" "${ROOTDIR}/go.mod"
|
||||
create_symlink "vendor.sum" "${ROOTDIR}/go.sum"
|
||||
|
||||
go 1.24.0
|
||||
EOF
|
||||
trap 'rm -f "${ROOTDIR}/go.mod"' EXIT
|
||||
if [ "${#cleanup_paths[@]}" -gt 0 ]; then
|
||||
trap 'rm -f "${cleanup_paths[@]}"' EXIT
|
||||
fi
|
||||
|
||||
GO111MODULE=on GOTOOLCHAIN=local "$@"
|
||||
|
||||
+31
-28
@@ -6,19 +6,21 @@ module github.com/docker/cli
|
||||
|
||||
go 1.25.0
|
||||
|
||||
tool golang.org/x/mod/modfile // for module compatibility check
|
||||
tool (
|
||||
github.com/cpuguy83/go-md2man/v2 // for scripts/docs/generate-man.sh
|
||||
golang.org/x/mod/modfile // for module compatibility check
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.2
|
||||
github.com/containerd/errdefs v1.0.0
|
||||
github.com/containerd/log v0.1.0
|
||||
github.com/containerd/platforms v1.0.0-rc.4
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/cli-docs-tool v0.11.0
|
||||
github.com/docker/distribution v2.8.3+incompatible
|
||||
github.com/docker/docker-credential-helpers v0.9.7
|
||||
github.com/docker/docker-credential-helpers v0.9.8
|
||||
github.com/docker/go-connections v0.7.0
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/fvbommel/sortorder v1.1.0
|
||||
@@ -28,15 +30,15 @@ require (
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/mattn/go-runewidth v0.0.23
|
||||
github.com/mattn/go-runewidth v0.0.24
|
||||
github.com/moby/go-archive v0.2.0
|
||||
github.com/moby/moby/api v1.54.2
|
||||
github.com/moby/moby/client v0.4.1
|
||||
github.com/moby/moby/api v1.55.0
|
||||
github.com/moby/moby/client v0.5.0
|
||||
github.com/moby/patternmatcher v0.6.1
|
||||
github.com/moby/swarmkit/v2 v2.1.2
|
||||
github.com/moby/sys/atomicwriter v0.1.0
|
||||
github.com/moby/sys/capability v0.4.0
|
||||
github.com/moby/sys/sequential v0.6.0
|
||||
github.com/moby/sys/sequential v0.7.0
|
||||
github.com/moby/sys/signal v0.7.1
|
||||
github.com/moby/sys/symlink v0.3.0
|
||||
github.com/moby/term v0.5.2
|
||||
@@ -49,19 +51,19 @@ require (
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0
|
||||
go.opentelemetry.io/otel v1.43.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0
|
||||
go.opentelemetry.io/otel/metric v1.43.0
|
||||
go.opentelemetry.io/otel/sdk v1.43.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0
|
||||
go.opentelemetry.io/otel/trace v1.43.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0
|
||||
go.opentelemetry.io/otel v1.44.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0
|
||||
go.opentelemetry.io/otel/metric v1.44.0
|
||||
go.opentelemetry.io/otel/sdk v1.44.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0
|
||||
go.opentelemetry.io/otel/trace v1.44.0
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/sys v0.44.0
|
||||
golang.org/x/term v0.43.0
|
||||
golang.org/x/text v0.37.0
|
||||
golang.org/x/sync v0.21.0
|
||||
golang.org/x/sys v0.46.0
|
||||
golang.org/x/term v0.44.0
|
||||
golang.org/x/text v0.38.0
|
||||
gotest.tools/v3 v3.5.2
|
||||
tags.cncf.io/container-device-interface v1.1.0
|
||||
)
|
||||
@@ -74,7 +76,8 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/docker/go-events v0.0.0-20250808211157-605354379745 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/docker/go-events v0.0.0-20260608200158-dbf6103125a4 // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
@@ -82,9 +85,9 @@ require (
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/klauspost/compress v1.18.6 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
@@ -98,13 +101,13 @@ require (
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
go.etcd.io/raft/v3 v3.6.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
golang.org/x/mod v0.36.0 // indirect
|
||||
golang.org/x/net v0.54.0 // indirect
|
||||
golang.org/x/mod v0.37.0 // indirect
|
||||
golang.org/x/net v0.56.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/grpc v1.80.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
google.golang.org/grpc v1.81.1 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
)
|
||||
|
||||
+54
-52
@@ -40,12 +40,12 @@ github.com/docker/cli-docs-tool v0.11.0 h1:7d8QARFb7QEobizqxmEM7fOteZEHwH/zWgHQt
|
||||
github.com/docker/cli-docs-tool v0.11.0/go.mod h1:ma8BKiisUo8D6W05XEYIh3oa1UbgrZhi1nowyKFJa8Q=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker-credential-helpers v0.9.7 h1:jaPIxEIDz5bQeghNAdzz0ETwMMnM4vzjZlxz3pWP4JA=
|
||||
github.com/docker/docker-credential-helpers v0.9.7/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
|
||||
github.com/docker/docker-credential-helpers v0.9.8 h1:bIREROb7So6PRlq6KTtdS9MPEjC29OQRkFNlvK2OX8Q=
|
||||
github.com/docker/docker-credential-helpers v0.9.8/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
|
||||
github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
|
||||
github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
|
||||
github.com/docker/go-events v0.0.0-20250808211157-605354379745 h1:yOn6Ze6IbYI/KAw2lw/83ELYvZh6hvsygTVkD0dzMC4=
|
||||
github.com/docker/go-events v0.0.0-20250808211157-605354379745/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||
github.com/docker/go-events v0.0.0-20260608200158-dbf6103125a4 h1:Bj+mzWc7MJqqD0UzTaPmwszW3ttOVjSFi84ZU5l+2I0=
|
||||
github.com/docker/go-events v0.0.0-20260608200158-dbf6103125a4/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
|
||||
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
@@ -87,8 +87,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
@@ -96,8 +96,8 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
||||
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -106,17 +106,17 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
|
||||
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
|
||||
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
|
||||
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
|
||||
github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg=
|
||||
github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
|
||||
github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY=
|
||||
github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ=
|
||||
github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc=
|
||||
github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
|
||||
github.com/moby/moby/client v0.5.0 h1:5XhyPk2fuOWf6RlSFa3MkIIgDZkF25xToXW8Q/BH7cc=
|
||||
github.com/moby/moby/client v0.5.0/go.mod h1:rcVpF8ncl9vo5gaIBdol6CnbEtSj1uxMvEV/UrykF/s=
|
||||
github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
|
||||
github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/swarmkit/v2 v2.1.2 h1:1WDZAI6HVYNKdCG4zlXnTAPyLsLwuhRGWlHoOUf5Z6I=
|
||||
@@ -125,8 +125,8 @@ github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w
|
||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||
github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=
|
||||
github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
github.com/moby/sys/sequential v0.7.0 h1:ASQNGNROJSuOO6LL6bPHbKvuZu6NU8P4ldPWk31zj/8=
|
||||
github.com/moby/sys/sequential v0.7.0/go.mod h1:NfSTAp6V3fw4tmkD62PEcOKeZKquXT8VKCkf7aVR79o=
|
||||
github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0=
|
||||
github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8=
|
||||
github.com/moby/sys/symlink v0.3.0 h1:GZX89mEZ9u53f97npBy4Rc3vJKj7JBDj/PN2I22GrNU=
|
||||
@@ -206,24 +206,26 @@ go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ=
|
||||
go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 h1:8UQVDcZxOJLtX6gxtDt3vY2WTgvZqMQRzjsqiIHQdkc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0/go.mod h1:2lmweYCiHYpEjQ/lSJBYhj9jP1zvCvQW4BqL9dnT7FQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI=
|
||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0 h1:SUplec5dp06reu1zaXmOXdvqH398taqrDXqUl99jxSc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0/go.mod h1:ho2g4N+ane+swq5I/VBkKWnRDY4kUINH3FuqyZqX/Ug=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo=
|
||||
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||
go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA=
|
||||
go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
|
||||
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
@@ -236,23 +238,23 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -261,14 +263,14 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -281,12 +283,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
|
||||
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
|
||||
+2
-1
@@ -78,7 +78,8 @@ func (b *Broadcaster) configure(ch chan configureRequest, sink Sink) error {
|
||||
select {
|
||||
case ch <- configureRequest{
|
||||
sink: sink,
|
||||
response: response}:
|
||||
response: response,
|
||||
}:
|
||||
ch = nil
|
||||
case err := <-response:
|
||||
return err
|
||||
|
||||
+4
-6
@@ -2,9 +2,7 @@ package events
|
||||
|
||||
import "fmt"
|
||||
|
||||
var (
|
||||
// ErrSinkClosed is returned if a write is issued to a sink that has been
|
||||
// closed. If encountered, the error should be considered terminal and
|
||||
// retries will not be successful.
|
||||
ErrSinkClosed = fmt.Errorf("events: sink closed")
|
||||
)
|
||||
// ErrSinkClosed is returned if a write is issued to a sink that has been
|
||||
// closed. If encountered, the error should be considered terminal and
|
||||
// retries will not be successful.
|
||||
var ErrSinkClosed = fmt.Errorf("events: sink closed")
|
||||
|
||||
+7
-9
@@ -173,15 +173,13 @@ func (b *Breaker) Failure(event Event, err error) bool {
|
||||
return false // never drop events.
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultExponentialBackoffConfig provides a default configuration for
|
||||
// exponential backoff.
|
||||
DefaultExponentialBackoffConfig = ExponentialBackoffConfig{
|
||||
Base: time.Second,
|
||||
Factor: time.Second,
|
||||
Max: 20 * time.Second,
|
||||
}
|
||||
)
|
||||
// DefaultExponentialBackoffConfig provides a default configuration for
|
||||
// exponential backoff.
|
||||
var DefaultExponentialBackoffConfig = ExponentialBackoffConfig{
|
||||
Base: time.Second,
|
||||
Factor: time.Second,
|
||||
Max: 20 * time.Second,
|
||||
}
|
||||
|
||||
// ExponentialBackoffConfig configures backoff parameters.
|
||||
//
|
||||
|
||||
+17
-1
@@ -71,6 +71,7 @@ type ServeMux struct {
|
||||
streamErrorHandler StreamErrorHandlerFunc
|
||||
routingErrorHandler RoutingErrorHandlerFunc
|
||||
disablePathLengthFallback bool
|
||||
disableHTTPMethodOverride bool
|
||||
unescapingMode UnescapingMode
|
||||
writeContentLength bool
|
||||
disableChunkedEncoding bool
|
||||
@@ -271,6 +272,19 @@ func WithDisablePathLengthFallback() ServeMuxOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithDisableHTTPMethodOverride returns a ServeMuxOption that disables the
|
||||
// X-HTTP-Method-Override header handling.
|
||||
//
|
||||
// When this option is used, the mux will no longer allow POST requests with
|
||||
// the X-HTTP-Method-Override header to override the HTTP method. The path
|
||||
// length fallback (POST with application/x-www-form-urlencoded falling back
|
||||
// to a matching GET handler) is not affected by this option.
|
||||
func WithDisableHTTPMethodOverride() ServeMuxOption {
|
||||
return func(serveMux *ServeMux) {
|
||||
serveMux.disableHTTPMethodOverride = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithWriteContentLength returns a ServeMuxOption to enable writing content length on non-streaming responses
|
||||
func WithWriteContentLength() ServeMuxOption {
|
||||
return func(serveMux *ServeMux) {
|
||||
@@ -405,7 +419,7 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
path = r.URL.RawPath
|
||||
}
|
||||
|
||||
if override := r.Header.Get("X-HTTP-Method-Override"); override != "" && s.isPathLengthFallback(r) {
|
||||
if override := r.Header.Get("X-HTTP-Method-Override"); override != "" && !s.disableHTTPMethodOverride && s.isPathLengthFallback(r) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
_, outboundMarshaler := MarshalerForRequest(s, r)
|
||||
sterr := status.Error(codes.InvalidArgument, err.Error())
|
||||
@@ -467,6 +481,7 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
Err: mse,
|
||||
})
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -509,6 +524,7 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
Err: mse,
|
||||
})
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
* -text
|
||||
*.bin -text -diff
|
||||
*.md text eol=lf
|
||||
|
||||
+700
-700
File diff suppressed because it is too large
Load Diff
+78
-78
@@ -1,79 +1,79 @@
|
||||
# Finite State Entropy
|
||||
|
||||
This package provides Finite State Entropy encoding and decoding.
|
||||
|
||||
Finite State Entropy (also referenced as [tANS](https://en.wikipedia.org/wiki/Asymmetric_numeral_systems#tANS))
|
||||
encoding provides a fast near-optimal symbol encoding/decoding
|
||||
for byte blocks as implemented in [zstandard](https://github.com/facebook/zstd).
|
||||
|
||||
This can be used for compressing input with a lot of similar input values to the smallest number of bytes.
|
||||
This does not perform any multi-byte [dictionary coding](https://en.wikipedia.org/wiki/Dictionary_coder) as LZ coders,
|
||||
but it can be used as a secondary step to compressors (like Snappy) that does not do entropy encoding.
|
||||
|
||||
* [Godoc documentation](https://godoc.org/github.com/klauspost/compress/fse)
|
||||
|
||||
## News
|
||||
|
||||
* Feb 2018: First implementation released. Consider this beta software for now.
|
||||
|
||||
# Usage
|
||||
|
||||
This package provides a low level interface that allows to compress single independent blocks.
|
||||
|
||||
Each block is separate, and there is no built in integrity checks.
|
||||
This means that the caller should keep track of block sizes and also do checksums if needed.
|
||||
|
||||
Compressing a block is done via the [`Compress`](https://godoc.org/github.com/klauspost/compress/fse#Compress) function.
|
||||
You must provide input and will receive the output and maybe an error.
|
||||
|
||||
These error values can be returned:
|
||||
|
||||
| Error | Description |
|
||||
|---------------------|-----------------------------------------------------------------------------|
|
||||
| `<nil>` | Everything ok, output is returned |
|
||||
| `ErrIncompressible` | Returned when input is judged to be too hard to compress |
|
||||
| `ErrUseRLE` | Returned from the compressor when the input is a single byte value repeated |
|
||||
| `(error)` | An internal error occurred. |
|
||||
|
||||
As can be seen above there are errors that will be returned even under normal operation so it is important to handle these.
|
||||
|
||||
To reduce allocations you can provide a [`Scratch`](https://godoc.org/github.com/klauspost/compress/fse#Scratch) object
|
||||
that can be re-used for successive calls. Both compression and decompression accepts a `Scratch` object, and the same
|
||||
object can be used for both.
|
||||
|
||||
Be aware, that when re-using a `Scratch` object that the *output* buffer is also re-used, so if you are still using this
|
||||
you must set the `Out` field in the scratch to nil. The same buffer is used for compression and decompression output.
|
||||
|
||||
Decompressing is done by calling the [`Decompress`](https://godoc.org/github.com/klauspost/compress/fse#Decompress) function.
|
||||
You must provide the output from the compression stage, at exactly the size you got back. If you receive an error back
|
||||
your input was likely corrupted.
|
||||
|
||||
It is important to note that a successful decoding does *not* mean your output matches your original input.
|
||||
There are no integrity checks, so relying on errors from the decompressor does not assure your data is valid.
|
||||
|
||||
For more detailed usage, see examples in the [godoc documentation](https://godoc.org/github.com/klauspost/compress/fse#pkg-examples).
|
||||
|
||||
# Performance
|
||||
|
||||
A lot of factors are affecting speed. Block sizes and compressibility of the material are primary factors.
|
||||
All compression functions are currently only running on the calling goroutine so only one core will be used per block.
|
||||
|
||||
The compressor is significantly faster if symbols are kept as small as possible. The highest byte value of the input
|
||||
is used to reduce some of the processing, so if all your input is above byte value 64 for instance, it may be
|
||||
beneficial to transpose all your input values down by 64.
|
||||
|
||||
With moderate block sizes around 64k speed are typically 200MB/s per core for compression and
|
||||
around 300MB/s decompression speed.
|
||||
|
||||
The same hardware typically does Huffman (deflate) encoding at 125MB/s and decompression at 100MB/s.
|
||||
|
||||
# Plans
|
||||
|
||||
At one point, more internals will be exposed to facilitate more "expert" usage of the components.
|
||||
|
||||
A streaming interface is also likely to be implemented. Likely compatible with [FSE stream format](https://github.com/Cyan4973/FiniteStateEntropy/blob/dev/programs/fileio.c#L261).
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions are always welcome. Be aware that adding public functions will require good justification and breaking
|
||||
# Finite State Entropy
|
||||
|
||||
This package provides Finite State Entropy encoding and decoding.
|
||||
|
||||
Finite State Entropy (also referenced as [tANS](https://en.wikipedia.org/wiki/Asymmetric_numeral_systems#tANS))
|
||||
encoding provides a fast near-optimal symbol encoding/decoding
|
||||
for byte blocks as implemented in [zstandard](https://github.com/facebook/zstd).
|
||||
|
||||
This can be used for compressing input with a lot of similar input values to the smallest number of bytes.
|
||||
This does not perform any multi-byte [dictionary coding](https://en.wikipedia.org/wiki/Dictionary_coder) as LZ coders,
|
||||
but it can be used as a secondary step to compressors (like Snappy) that does not do entropy encoding.
|
||||
|
||||
* [Godoc documentation](https://godoc.org/github.com/klauspost/compress/fse)
|
||||
|
||||
## News
|
||||
|
||||
* Feb 2018: First implementation released. Consider this beta software for now.
|
||||
|
||||
# Usage
|
||||
|
||||
This package provides a low level interface that allows to compress single independent blocks.
|
||||
|
||||
Each block is separate, and there is no built in integrity checks.
|
||||
This means that the caller should keep track of block sizes and also do checksums if needed.
|
||||
|
||||
Compressing a block is done via the [`Compress`](https://godoc.org/github.com/klauspost/compress/fse#Compress) function.
|
||||
You must provide input and will receive the output and maybe an error.
|
||||
|
||||
These error values can be returned:
|
||||
|
||||
| Error | Description |
|
||||
|---------------------|-----------------------------------------------------------------------------|
|
||||
| `<nil>` | Everything ok, output is returned |
|
||||
| `ErrIncompressible` | Returned when input is judged to be too hard to compress |
|
||||
| `ErrUseRLE` | Returned from the compressor when the input is a single byte value repeated |
|
||||
| `(error)` | An internal error occurred. |
|
||||
|
||||
As can be seen above there are errors that will be returned even under normal operation so it is important to handle these.
|
||||
|
||||
To reduce allocations you can provide a [`Scratch`](https://godoc.org/github.com/klauspost/compress/fse#Scratch) object
|
||||
that can be re-used for successive calls. Both compression and decompression accepts a `Scratch` object, and the same
|
||||
object can be used for both.
|
||||
|
||||
Be aware, that when re-using a `Scratch` object that the *output* buffer is also re-used, so if you are still using this
|
||||
you must set the `Out` field in the scratch to nil. The same buffer is used for compression and decompression output.
|
||||
|
||||
Decompressing is done by calling the [`Decompress`](https://godoc.org/github.com/klauspost/compress/fse#Decompress) function.
|
||||
You must provide the output from the compression stage, at exactly the size you got back. If you receive an error back
|
||||
your input was likely corrupted.
|
||||
|
||||
It is important to note that a successful decoding does *not* mean your output matches your original input.
|
||||
There are no integrity checks, so relying on errors from the decompressor does not assure your data is valid.
|
||||
|
||||
For more detailed usage, see examples in the [godoc documentation](https://godoc.org/github.com/klauspost/compress/fse#pkg-examples).
|
||||
|
||||
# Performance
|
||||
|
||||
A lot of factors are affecting speed. Block sizes and compressibility of the material are primary factors.
|
||||
All compression functions are currently only running on the calling goroutine so only one core will be used per block.
|
||||
|
||||
The compressor is significantly faster if symbols are kept as small as possible. The highest byte value of the input
|
||||
is used to reduce some of the processing, so if all your input is above byte value 64 for instance, it may be
|
||||
beneficial to transpose all your input values down by 64.
|
||||
|
||||
With moderate block sizes around 64k speed are typically 200MB/s per core for compression and
|
||||
around 300MB/s decompression speed.
|
||||
|
||||
The same hardware typically does Huffman (deflate) encoding at 125MB/s and decompression at 100MB/s.
|
||||
|
||||
# Plans
|
||||
|
||||
At one point, more internals will be exposed to facilitate more "expert" usage of the components.
|
||||
|
||||
A streaming interface is also likely to be implemented. Likely compatible with [FSE stream format](https://github.com/Cyan4973/FiniteStateEntropy/blob/dev/programs/fileio.c#L261).
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions are always welcome. Be aware that adding public functions will require good justification and breaking
|
||||
changes will likely not be accepted. If in doubt open an issue before writing the PR.
|
||||
+89
-89
@@ -1,89 +1,89 @@
|
||||
# Huff0 entropy compression
|
||||
|
||||
This package provides Huff0 encoding and decoding as used in zstd.
|
||||
|
||||
[Huff0](https://github.com/Cyan4973/FiniteStateEntropy#new-generation-entropy-coders),
|
||||
a Huffman codec designed for modern CPU, featuring OoO (Out of Order) operations on multiple ALU
|
||||
(Arithmetic Logic Unit), achieving extremely fast compression and decompression speeds.
|
||||
|
||||
This can be used for compressing input with a lot of similar input values to the smallest number of bytes.
|
||||
This does not perform any multi-byte [dictionary coding](https://en.wikipedia.org/wiki/Dictionary_coder) as LZ coders,
|
||||
but it can be used as a secondary step to compressors (like Snappy) that does not do entropy encoding.
|
||||
|
||||
* [Godoc documentation](https://godoc.org/github.com/klauspost/compress/huff0)
|
||||
|
||||
## News
|
||||
|
||||
This is used as part of the [zstandard](https://github.com/klauspost/compress/tree/master/zstd#zstd) compression and decompression package.
|
||||
|
||||
This ensures that most functionality is well tested.
|
||||
|
||||
# Usage
|
||||
|
||||
This package provides a low level interface that allows to compress single independent blocks.
|
||||
|
||||
Each block is separate, and there is no built in integrity checks.
|
||||
This means that the caller should keep track of block sizes and also do checksums if needed.
|
||||
|
||||
Compressing a block is done via the [`Compress1X`](https://godoc.org/github.com/klauspost/compress/huff0#Compress1X) and
|
||||
[`Compress4X`](https://godoc.org/github.com/klauspost/compress/huff0#Compress4X) functions.
|
||||
You must provide input and will receive the output and maybe an error.
|
||||
|
||||
These error values can be returned:
|
||||
|
||||
| Error | Description |
|
||||
|---------------------|-----------------------------------------------------------------------------|
|
||||
| `<nil>` | Everything ok, output is returned |
|
||||
| `ErrIncompressible` | Returned when input is judged to be too hard to compress |
|
||||
| `ErrUseRLE` | Returned from the compressor when the input is a single byte value repeated |
|
||||
| `ErrTooBig` | Returned if the input block exceeds the maximum allowed size (128 Kib) |
|
||||
| `(error)` | An internal error occurred. |
|
||||
|
||||
|
||||
As can be seen above some of there are errors that will be returned even under normal operation so it is important to handle these.
|
||||
|
||||
To reduce allocations you can provide a [`Scratch`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch) object
|
||||
that can be re-used for successive calls. Both compression and decompression accepts a `Scratch` object, and the same
|
||||
object can be used for both.
|
||||
|
||||
Be aware, that when re-using a `Scratch` object that the *output* buffer is also re-used, so if you are still using this
|
||||
you must set the `Out` field in the scratch to nil. The same buffer is used for compression and decompression output.
|
||||
|
||||
The `Scratch` object will retain state that allows to re-use previous tables for encoding and decoding.
|
||||
|
||||
## Tables and re-use
|
||||
|
||||
Huff0 allows for reusing tables from the previous block to save space if that is expected to give better/faster results.
|
||||
|
||||
The Scratch object allows you to set a [`ReusePolicy`](https://godoc.org/github.com/klauspost/compress/huff0#ReusePolicy)
|
||||
that controls this behaviour. See the documentation for details. This can be altered between each block.
|
||||
|
||||
Do however note that this information is *not* stored in the output block and it is up to the users of the package to
|
||||
record whether [`ReadTable`](https://godoc.org/github.com/klauspost/compress/huff0#ReadTable) should be called,
|
||||
based on the boolean reported back from the CompressXX call.
|
||||
|
||||
If you want to store the table separate from the data, you can access them as `OutData` and `OutTable` on the
|
||||
[`Scratch`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch) object.
|
||||
|
||||
## Decompressing
|
||||
|
||||
The first part of decoding is to initialize the decoding table through [`ReadTable`](https://godoc.org/github.com/klauspost/compress/huff0#ReadTable).
|
||||
This will initialize the decoding tables.
|
||||
You can supply the complete block to `ReadTable` and it will return the data part of the block
|
||||
which can be given to the decompressor.
|
||||
|
||||
Decompressing is done by calling the [`Decompress1X`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch.Decompress1X)
|
||||
or [`Decompress4X`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch.Decompress4X) function.
|
||||
|
||||
For concurrently decompressing content with a fixed table a stateless [`Decoder`](https://godoc.org/github.com/klauspost/compress/huff0#Decoder) can be requested which will remain correct as long as the scratch is unchanged. The capacity of the provided slice indicates the expected output size.
|
||||
|
||||
You must provide the output from the compression stage, at exactly the size you got back. If you receive an error back
|
||||
your input was likely corrupted.
|
||||
|
||||
It is important to note that a successful decoding does *not* mean your output matches your original input.
|
||||
There are no integrity checks, so relying on errors from the decompressor does not assure your data is valid.
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions are always welcome. Be aware that adding public functions will require good justification and breaking
|
||||
changes will likely not be accepted. If in doubt open an issue before writing the PR.
|
||||
# Huff0 entropy compression
|
||||
|
||||
This package provides Huff0 encoding and decoding as used in zstd.
|
||||
|
||||
[Huff0](https://github.com/Cyan4973/FiniteStateEntropy#new-generation-entropy-coders),
|
||||
a Huffman codec designed for modern CPU, featuring OoO (Out of Order) operations on multiple ALU
|
||||
(Arithmetic Logic Unit), achieving extremely fast compression and decompression speeds.
|
||||
|
||||
This can be used for compressing input with a lot of similar input values to the smallest number of bytes.
|
||||
This does not perform any multi-byte [dictionary coding](https://en.wikipedia.org/wiki/Dictionary_coder) as LZ coders,
|
||||
but it can be used as a secondary step to compressors (like Snappy) that does not do entropy encoding.
|
||||
|
||||
* [Godoc documentation](https://godoc.org/github.com/klauspost/compress/huff0)
|
||||
|
||||
## News
|
||||
|
||||
This is used as part of the [zstandard](https://github.com/klauspost/compress/tree/master/zstd#zstd) compression and decompression package.
|
||||
|
||||
This ensures that most functionality is well tested.
|
||||
|
||||
# Usage
|
||||
|
||||
This package provides a low level interface that allows to compress single independent blocks.
|
||||
|
||||
Each block is separate, and there is no built in integrity checks.
|
||||
This means that the caller should keep track of block sizes and also do checksums if needed.
|
||||
|
||||
Compressing a block is done via the [`Compress1X`](https://godoc.org/github.com/klauspost/compress/huff0#Compress1X) and
|
||||
[`Compress4X`](https://godoc.org/github.com/klauspost/compress/huff0#Compress4X) functions.
|
||||
You must provide input and will receive the output and maybe an error.
|
||||
|
||||
These error values can be returned:
|
||||
|
||||
| Error | Description |
|
||||
|---------------------|-----------------------------------------------------------------------------|
|
||||
| `<nil>` | Everything ok, output is returned |
|
||||
| `ErrIncompressible` | Returned when input is judged to be too hard to compress |
|
||||
| `ErrUseRLE` | Returned from the compressor when the input is a single byte value repeated |
|
||||
| `ErrTooBig` | Returned if the input block exceeds the maximum allowed size (128 Kib) |
|
||||
| `(error)` | An internal error occurred. |
|
||||
|
||||
|
||||
As can be seen above some of there are errors that will be returned even under normal operation so it is important to handle these.
|
||||
|
||||
To reduce allocations you can provide a [`Scratch`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch) object
|
||||
that can be re-used for successive calls. Both compression and decompression accepts a `Scratch` object, and the same
|
||||
object can be used for both.
|
||||
|
||||
Be aware, that when re-using a `Scratch` object that the *output* buffer is also re-used, so if you are still using this
|
||||
you must set the `Out` field in the scratch to nil. The same buffer is used for compression and decompression output.
|
||||
|
||||
The `Scratch` object will retain state that allows to re-use previous tables for encoding and decoding.
|
||||
|
||||
## Tables and re-use
|
||||
|
||||
Huff0 allows for reusing tables from the previous block to save space if that is expected to give better/faster results.
|
||||
|
||||
The Scratch object allows you to set a [`ReusePolicy`](https://godoc.org/github.com/klauspost/compress/huff0#ReusePolicy)
|
||||
that controls this behaviour. See the documentation for details. This can be altered between each block.
|
||||
|
||||
Do however note that this information is *not* stored in the output block and it is up to the users of the package to
|
||||
record whether [`ReadTable`](https://godoc.org/github.com/klauspost/compress/huff0#ReadTable) should be called,
|
||||
based on the boolean reported back from the CompressXX call.
|
||||
|
||||
If you want to store the table separate from the data, you can access them as `OutData` and `OutTable` on the
|
||||
[`Scratch`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch) object.
|
||||
|
||||
## Decompressing
|
||||
|
||||
The first part of decoding is to initialize the decoding table through [`ReadTable`](https://godoc.org/github.com/klauspost/compress/huff0#ReadTable).
|
||||
This will initialize the decoding tables.
|
||||
You can supply the complete block to `ReadTable` and it will return the data part of the block
|
||||
which can be given to the decompressor.
|
||||
|
||||
Decompressing is done by calling the [`Decompress1X`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch.Decompress1X)
|
||||
or [`Decompress4X`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch.Decompress4X) function.
|
||||
|
||||
For concurrently decompressing content with a fixed table a stateless [`Decoder`](https://godoc.org/github.com/klauspost/compress/huff0#Decoder) can be requested which will remain correct as long as the scratch is unchanged. The capacity of the provided slice indicates the expected output size.
|
||||
|
||||
You must provide the output from the compression stage, at exactly the size you got back. If you receive an error back
|
||||
your input was likely corrupted.
|
||||
|
||||
It is important to note that a successful decoding does *not* mean your output matches your original input.
|
||||
There are no integrity checks, so relying on errors from the decompressor does not assure your data is valid.
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions are always welcome. Be aware that adding public functions will require good justification and breaking
|
||||
changes will likely not be accepted. If in doubt open an issue before writing the PR.
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
The following versions of go-runewidth are currently supported with
|
||||
security updates.
|
||||
|
||||
| Version | Supported |
|
||||
| -------- | ------------------ |
|
||||
| 0.0.23 | :white_check_mark: |
|
||||
| < 0.0.23 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you discover a security vulnerability in go-runewidth, please report it
|
||||
privately via GitHub's "Report a vulnerability" feature on the Security tab
|
||||
of the repository (https://github.com/mattn/go-runewidth/security), or by
|
||||
emailing the maintainer at mattn.jp@gmail.com.
|
||||
|
||||
Please include a description of the issue, reproduction steps, and the
|
||||
affected version. You can expect an initial response within one week. If
|
||||
the vulnerability is accepted, a fix will be prepared and a new release
|
||||
will be published; you will be credited in the release notes unless you
|
||||
request otherwise. If the report is declined, you will receive an
|
||||
explanation of the reasoning.
|
||||
+118
-35
@@ -2,6 +2,7 @@ package runewidth
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
@@ -25,13 +26,19 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
zerowidth table // combining + nonprint merged for faster zero-width lookup
|
||||
widewidth table // ambiguous + doublewidth merged for EA path
|
||||
zerowidth table // combining + nonprint merged for faster zero-width lookup
|
||||
widewidth table // ambiguous + doublewidth merged for EA path
|
||||
eastAsianWidth widthTable
|
||||
eastAsianWidth0 [0x300]byte
|
||||
)
|
||||
|
||||
func init() {
|
||||
zerowidth = mergeIntervals(combining, nonprint)
|
||||
widewidth = mergeIntervals(ambiguous, doublewidth)
|
||||
eastAsianWidth = makeWidthTable(zerowidth, widewidth)
|
||||
for r := range eastAsianWidth0 {
|
||||
eastAsianWidth0[r] = byte(runeWidthEastAsian(rune(r)))
|
||||
}
|
||||
handleEnv()
|
||||
}
|
||||
|
||||
@@ -90,6 +97,14 @@ type interval struct {
|
||||
|
||||
type table []interval
|
||||
|
||||
type widthInterval struct {
|
||||
first rune
|
||||
last rune
|
||||
width byte
|
||||
}
|
||||
|
||||
type widthTable []widthInterval
|
||||
|
||||
func inTable(r rune, t table) bool {
|
||||
if r < t[0].first {
|
||||
return false
|
||||
@@ -116,6 +131,71 @@ func inTable(r rune, t table) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func makeWidthTable(zero, two table) widthTable {
|
||||
wt := make(widthTable, 0, len(zero)+len(two))
|
||||
zi := 0
|
||||
for _, iv := range two {
|
||||
start := iv.first
|
||||
for zi < len(zero) && zero[zi].last < start {
|
||||
zi++
|
||||
}
|
||||
for i := zi; i < len(zero) && zero[i].first <= iv.last; i++ {
|
||||
if start < zero[i].first {
|
||||
wt = append(wt, widthInterval{start, zero[i].first - 1, 2})
|
||||
}
|
||||
if start <= zero[i].last {
|
||||
start = zero[i].last + 1
|
||||
}
|
||||
if start > iv.last {
|
||||
break
|
||||
}
|
||||
}
|
||||
if start <= iv.last {
|
||||
wt = append(wt, widthInterval{start, iv.last, 2})
|
||||
}
|
||||
}
|
||||
for _, iv := range zero {
|
||||
wt = append(wt, widthInterval{iv.first, iv.last, 0})
|
||||
}
|
||||
sort.Slice(wt, func(i, j int) bool {
|
||||
return wt[i].first < wt[j].first
|
||||
})
|
||||
return wt
|
||||
}
|
||||
|
||||
func inWidthTable(r rune, t widthTable) (int, bool) {
|
||||
if r < t[0].first {
|
||||
return 0, false
|
||||
}
|
||||
if r > t[len(t)-1].last {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
bot := 0
|
||||
top := len(t) - 1
|
||||
for top >= bot {
|
||||
mid := (bot + top) >> 1
|
||||
|
||||
switch {
|
||||
case t[mid].last < r:
|
||||
bot = mid + 1
|
||||
case t[mid].first > r:
|
||||
top = mid - 1
|
||||
default:
|
||||
return int(t[mid].width), true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func runeWidthEastAsian(r rune) int {
|
||||
if w, ok := inWidthTable(r, eastAsianWidth); ok {
|
||||
return w
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
var private = table{
|
||||
{0x00E000, 0x00F8FF}, {0x0F0000, 0x0FFFFD}, {0x100000, 0x10FFFD},
|
||||
}
|
||||
@@ -153,13 +233,16 @@ func (c *Condition) RuneWidth(r rune) int {
|
||||
}
|
||||
// optimized version, verified by TestRuneWidthChecksums()
|
||||
if !c.EastAsianWidth {
|
||||
switch {
|
||||
case r < 0x20:
|
||||
if r < 0x20 {
|
||||
return 0
|
||||
case (r >= 0x7F && r <= 0x9F) || r == 0xAD: // nonprint
|
||||
}
|
||||
if (r >= 0x7F && r <= 0x9F) || r == 0xAD { // nonprint
|
||||
return 0
|
||||
case r < 0x300:
|
||||
}
|
||||
if r < 0x300 {
|
||||
return 1
|
||||
}
|
||||
switch {
|
||||
case inTable(r, zerowidth):
|
||||
return 0
|
||||
case inTable(r, doublewidth):
|
||||
@@ -167,20 +250,18 @@ func (c *Condition) RuneWidth(r rune) int {
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
case inTable(r, zerowidth):
|
||||
return 0
|
||||
case inTable(r, narrow):
|
||||
return 1
|
||||
case inTable(r, widewidth):
|
||||
return 2
|
||||
case !c.StrictEmojiNeutral && inTable(r, emoji):
|
||||
return 2
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
if r < 0x300 {
|
||||
return int(eastAsianWidth0[r])
|
||||
}
|
||||
if w, ok := inWidthTable(r, eastAsianWidth); ok {
|
||||
return w
|
||||
}
|
||||
if !c.StrictEmojiNeutral && inTable(r, emoji) {
|
||||
return 2
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
// CreateLUT will create an in-memory lookup table of 557056 bytes for faster operation.
|
||||
@@ -206,6 +287,13 @@ func (c *Condition) CreateLUT() {
|
||||
|
||||
// StringWidth return width as you can see
|
||||
func (c *Condition) StringWidth(s string) (width int) {
|
||||
if len(s) == 1 {
|
||||
b := s[0]
|
||||
if b < 0x20 || b == 0x7F {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
if len(s) > 0 && len(s) <= utf8.UTFMax {
|
||||
r, size := utf8.DecodeRuneInString(s)
|
||||
if size == len(s) {
|
||||
@@ -213,15 +301,19 @@ func (c *Condition) StringWidth(s string) (width int) {
|
||||
}
|
||||
}
|
||||
// ASCII fast path: no grapheme clustering needed for pure ASCII
|
||||
if isAllASCII(s) {
|
||||
for i := 0; i < len(s); i++ {
|
||||
b := s[i]
|
||||
if b >= 0x20 && b != 0x7F {
|
||||
width++
|
||||
}
|
||||
for i := 0; i < len(s); i++ {
|
||||
b := s[i]
|
||||
if b >= 0x80 {
|
||||
goto graphemes
|
||||
}
|
||||
if b >= 0x20 && b != 0x7F {
|
||||
width++
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
|
||||
graphemes:
|
||||
width = 0
|
||||
g := graphemes.FromString(s)
|
||||
for g.Next() {
|
||||
var chWidth int
|
||||
@@ -236,15 +328,6 @@ func (c *Condition) StringWidth(s string) (width int) {
|
||||
return
|
||||
}
|
||||
|
||||
func isAllASCII(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] >= 0x80 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Truncate return string truncated with w cells
|
||||
func (c *Condition) Truncate(s string, w int, tail string) string {
|
||||
if c.StringWidth(s) <= w {
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// AttestationStatement is a single in-toto statement attached to an image.
|
||||
type AttestationStatement struct {
|
||||
// Descriptor is the OCI descriptor of the statement blob (media type,
|
||||
// digest, size, annotations).
|
||||
Descriptor ocispec.Descriptor `json:"Descriptor"`
|
||||
// PredicateType is the in-toto predicate type URI of this statement.
|
||||
PredicateType string `json:"PredicateType"`
|
||||
// Statement is the verbatim in-toto statement JSON. Omitted unless the
|
||||
// caller opts in via the statement=true query parameter.
|
||||
Statement *json.RawMessage `json:"Statement,omitempty"`
|
||||
}
|
||||
+1
-1
@@ -109,7 +109,7 @@ const DummyHost = "api.moby.localhost"
|
||||
// overriding the version and disable API-version negotiation.
|
||||
//
|
||||
// This version may be lower than the version of the api library module used.
|
||||
const MaxAPIVersion = "1.54"
|
||||
const MaxAPIVersion = "1.55"
|
||||
|
||||
// MinAPIVersion is the minimum API version supported by the client. API versions
|
||||
// below this version are not considered when performing API-version negotiation.
|
||||
|
||||
+1
@@ -133,6 +133,7 @@ type ImageAPIClient interface {
|
||||
|
||||
ImageInspect(ctx context.Context, image string, _ ...ImageInspectOption) (ImageInspectResult, error)
|
||||
ImageHistory(ctx context.Context, image string, _ ...ImageHistoryOption) (ImageHistoryResult, error)
|
||||
ImageAttestations(ctx context.Context, image string, _ ...ImageAttestationsOption) (ImageAttestationsResult, error)
|
||||
|
||||
ImageLoad(ctx context.Context, input io.Reader, _ ...ImageLoadOption) (ImageLoadResult, error)
|
||||
ImageSave(ctx context.Context, images []string, _ ...ImageSaveOption) (ImageSaveResult, error)
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// ImageAttestations returns the in-toto attestation statements attached to an
|
||||
// image for the given platform. This requires API version 1.55 or higher.
|
||||
func (cli *Client) ImageAttestations(ctx context.Context, imageID string, opts ...ImageAttestationsOption) (ImageAttestationsResult, error) {
|
||||
if imageID == "" {
|
||||
return ImageAttestationsResult{}, objectNotFoundError{object: "image", id: imageID}
|
||||
}
|
||||
|
||||
if err := cli.requiresVersion(ctx, "1.55", "attestations"); err != nil {
|
||||
return ImageAttestationsResult{}, err
|
||||
}
|
||||
|
||||
var o imageAttestationsOpts
|
||||
for _, opt := range opts {
|
||||
if err := opt.Apply(&o); err != nil {
|
||||
return ImageAttestationsResult{}, err
|
||||
}
|
||||
}
|
||||
|
||||
query := url.Values{}
|
||||
if o.platform != nil {
|
||||
p, err := encodePlatform(o.platform)
|
||||
if err != nil {
|
||||
return ImageAttestationsResult{}, err
|
||||
}
|
||||
query.Set("platform", p)
|
||||
}
|
||||
for _, pt := range o.predicateTypes {
|
||||
query.Add("type", pt)
|
||||
}
|
||||
if o.includeStatement {
|
||||
query.Set("statement", "1")
|
||||
}
|
||||
|
||||
resp, err := cli.get(ctx, "/images/"+imageID+"/attestations", query, nil)
|
||||
defer ensureReaderClosed(resp)
|
||||
if err != nil {
|
||||
return ImageAttestationsResult{}, err
|
||||
}
|
||||
|
||||
var result ImageAttestationsResult
|
||||
err = json.NewDecoder(resp.Body).Decode(&result.Items)
|
||||
return result, err
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/moby/moby/api/types/image"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// ImageAttestationsResult is the result of an ImageAttestations operation.
|
||||
type ImageAttestationsResult struct {
|
||||
Items []image.AttestationStatement
|
||||
}
|
||||
|
||||
// ImageAttestationsOption is a functional option for the ImageAttestations operation.
|
||||
type ImageAttestationsOption interface {
|
||||
Apply(*imageAttestationsOpts) error
|
||||
}
|
||||
|
||||
type imageAttestationsOptionFunc func(*imageAttestationsOpts) error
|
||||
|
||||
func (f imageAttestationsOptionFunc) Apply(o *imageAttestationsOpts) error { return f(o) }
|
||||
|
||||
type imageAttestationsOpts struct {
|
||||
platform *ocispec.Platform
|
||||
predicateTypes []string
|
||||
includeStatement bool
|
||||
}
|
||||
|
||||
// ImageAttestationsWithPlatform filters attestations to those for the given
|
||||
// platform variant. If omitted, the daemon's default platform is used.
|
||||
func ImageAttestationsWithPlatform(platform ocispec.Platform) ImageAttestationsOption {
|
||||
return imageAttestationsOptionFunc(func(o *imageAttestationsOpts) error {
|
||||
o.platform = &platform
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ImageAttestationsWithPredicateTypes filters returned statements to those
|
||||
// whose in-toto predicate type matches one of the given URIs.
|
||||
// If not set, all statements are returned.
|
||||
func ImageAttestationsWithPredicateTypes(types ...string) ImageAttestationsOption {
|
||||
return imageAttestationsOptionFunc(func(o *imageAttestationsOpts) error {
|
||||
o.predicateTypes = append(o.predicateTypes, types...)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ImageAttestationsWithStatement asks the daemon to include the verbatim
|
||||
// in-toto statement body in each returned entry. Without this option, only
|
||||
// the descriptor and predicate type are returned and statement blobs are
|
||||
// not read.
|
||||
func ImageAttestationsWithStatement() ImageAttestationsOption {
|
||||
return imageAttestationsOptionFunc(func(o *imageAttestationsOpts) error {
|
||||
o.includeStatement = true
|
||||
return nil
|
||||
})
|
||||
}
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package sequential
|
||||
|
||||
|
||||
+16
-87
@@ -1,3 +1,5 @@
|
||||
//go:build windows
|
||||
|
||||
package sequential
|
||||
|
||||
import (
|
||||
@@ -6,108 +8,36 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// Create is a copy of [os.Create], modified to use sequential file access.
|
||||
//
|
||||
// It uses [windows.FILE_FLAG_SEQUENTIAL_SCAN] rather than [windows.FILE_ATTRIBUTE_NORMAL]
|
||||
// as implemented in golang. Refer to the [Win32 API documentation] for details
|
||||
// on sequential file access.
|
||||
// It uses the Windows sequential scan file flag. Refer to the [Win32 API
|
||||
// documentation] for details on sequential file access.
|
||||
//
|
||||
// [Win32 API documentation]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#FILE_FLAG_SEQUENTIAL_SCAN
|
||||
func Create(name string) (*os.File, error) {
|
||||
return openFileSequential(name, windows.O_RDWR|windows.O_CREAT|windows.O_TRUNC)
|
||||
return openFileSequential(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666)
|
||||
}
|
||||
|
||||
// Open is a copy of [os.Open], modified to use sequential file access.
|
||||
//
|
||||
// It uses [windows.FILE_FLAG_SEQUENTIAL_SCAN] rather than [windows.FILE_ATTRIBUTE_NORMAL]
|
||||
// as implemented in golang. Refer to the [Win32 API documentation] for details
|
||||
// on sequential file access.
|
||||
// It uses the Windows sequential scan file flag. Refer to the [Win32 API
|
||||
// documentation] for details on sequential file access.
|
||||
//
|
||||
// [Win32 API documentation]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#FILE_FLAG_SEQUENTIAL_SCAN
|
||||
func Open(name string) (*os.File, error) {
|
||||
return openFileSequential(name, windows.O_RDONLY)
|
||||
return openFileSequential(name, os.O_RDONLY, 0)
|
||||
}
|
||||
|
||||
// OpenFile is a copy of [os.OpenFile], modified to use sequential file access.
|
||||
//
|
||||
// It uses [windows.FILE_FLAG_SEQUENTIAL_SCAN] rather than [windows.FILE_ATTRIBUTE_NORMAL]
|
||||
// as implemented in golang. Refer to the [Win32 API documentation] for details
|
||||
// on sequential file access.
|
||||
// It uses the Windows sequential scan file flag. Refer to the [Win32 API
|
||||
// documentation] for details on sequential file access.
|
||||
//
|
||||
// [Win32 API documentation]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#FILE_FLAG_SEQUENTIAL_SCAN
|
||||
func OpenFile(name string, flag int, _ os.FileMode) (*os.File, error) {
|
||||
return openFileSequential(name, flag)
|
||||
}
|
||||
|
||||
func openFileSequential(name string, flag int) (file *os.File, err error) {
|
||||
if name == "" {
|
||||
return nil, &os.PathError{Op: "open", Path: name, Err: windows.ERROR_FILE_NOT_FOUND}
|
||||
}
|
||||
r, e := openSequential(name, flag|windows.O_CLOEXEC)
|
||||
if e != nil {
|
||||
return nil, &os.PathError{Op: "open", Path: name, Err: e}
|
||||
}
|
||||
return os.NewFile(uintptr(r), name), nil
|
||||
}
|
||||
|
||||
func makeInheritSa() *windows.SecurityAttributes {
|
||||
var sa windows.SecurityAttributes
|
||||
sa.Length = uint32(unsafe.Sizeof(sa))
|
||||
sa.InheritHandle = 1
|
||||
return &sa
|
||||
}
|
||||
|
||||
func openSequential(path string, mode int) (fd windows.Handle, err error) {
|
||||
if len(path) == 0 {
|
||||
return windows.InvalidHandle, windows.ERROR_FILE_NOT_FOUND
|
||||
}
|
||||
pathp, err := windows.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return windows.InvalidHandle, err
|
||||
}
|
||||
var access uint32
|
||||
switch mode & (windows.O_RDONLY | windows.O_WRONLY | windows.O_RDWR) {
|
||||
case windows.O_RDONLY:
|
||||
access = windows.GENERIC_READ
|
||||
case windows.O_WRONLY:
|
||||
access = windows.GENERIC_WRITE
|
||||
case windows.O_RDWR:
|
||||
access = windows.GENERIC_READ | windows.GENERIC_WRITE
|
||||
}
|
||||
if mode&windows.O_CREAT != 0 {
|
||||
access |= windows.GENERIC_WRITE
|
||||
}
|
||||
if mode&windows.O_APPEND != 0 {
|
||||
access &^= windows.GENERIC_WRITE
|
||||
access |= windows.FILE_APPEND_DATA
|
||||
}
|
||||
sharemode := uint32(windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE)
|
||||
var sa *windows.SecurityAttributes
|
||||
if mode&windows.O_CLOEXEC == 0 {
|
||||
sa = makeInheritSa()
|
||||
}
|
||||
var createmode uint32
|
||||
switch {
|
||||
case mode&(windows.O_CREAT|windows.O_EXCL) == (windows.O_CREAT | windows.O_EXCL):
|
||||
createmode = windows.CREATE_NEW
|
||||
case mode&(windows.O_CREAT|windows.O_TRUNC) == (windows.O_CREAT | windows.O_TRUNC):
|
||||
createmode = windows.CREATE_ALWAYS
|
||||
case mode&windows.O_CREAT == windows.O_CREAT:
|
||||
createmode = windows.OPEN_ALWAYS
|
||||
case mode&windows.O_TRUNC == windows.O_TRUNC:
|
||||
createmode = windows.TRUNCATE_EXISTING
|
||||
default:
|
||||
createmode = windows.OPEN_EXISTING
|
||||
}
|
||||
// Use FILE_FLAG_SEQUENTIAL_SCAN rather than FILE_ATTRIBUTE_NORMAL as implemented in golang.
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#FILE_FLAG_SEQUENTIAL_SCAN
|
||||
h, e := windows.CreateFile(pathp, access, sharemode, sa, createmode, windows.FILE_FLAG_SEQUENTIAL_SCAN, 0)
|
||||
return h, e
|
||||
func OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) {
|
||||
return openFileSequential(name, flag, perm)
|
||||
}
|
||||
|
||||
// Helpers for CreateTemp
|
||||
@@ -134,9 +64,8 @@ func nextSuffix() string {
|
||||
|
||||
// CreateTemp is a copy of [os.CreateTemp], modified to use sequential file access.
|
||||
//
|
||||
// It uses [windows.FILE_FLAG_SEQUENTIAL_SCAN] rather than [windows.FILE_ATTRIBUTE_NORMAL]
|
||||
// as implemented in golang. Refer to the [Win32 API documentation] for details
|
||||
// on sequential file access.
|
||||
// It uses the Windows sequential scan file flag. Refer to the [Win32 API
|
||||
// documentation] for details on sequential file access.
|
||||
//
|
||||
// [Win32 API documentation]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#FILE_FLAG_SEQUENTIAL_SCAN
|
||||
func CreateTemp(dir, prefix string) (f *os.File, err error) {
|
||||
@@ -145,9 +74,9 @@ func CreateTemp(dir, prefix string) (f *os.File, err error) {
|
||||
}
|
||||
|
||||
nconflict := 0
|
||||
for i := 0; i < 10000; i++ {
|
||||
for range 10000 {
|
||||
name := filepath.Join(dir, prefix+nextSuffix())
|
||||
f, err = openFileSequential(name, windows.O_RDWR|windows.O_CREAT|windows.O_EXCL)
|
||||
f, err = openFileSequential(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o600)
|
||||
if os.IsExist(err) {
|
||||
if nconflict++; nconflict > 10 {
|
||||
randmu.Lock()
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
//go:build windows && go1.26
|
||||
|
||||
package sequential
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func openFileSequential(name string, flag int, perm os.FileMode) (*os.File, error) {
|
||||
return os.OpenFile(name, flag|windows.O_FILE_FLAG_SEQUENTIAL_SCAN, perm)
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
//go:build windows && !go1.26
|
||||
|
||||
package sequential
|
||||
|
||||
import (
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func openFileSequential(name string, flag int, _ os.FileMode) (file *os.File, err error) {
|
||||
if name == "" {
|
||||
return nil, &os.PathError{Op: "open", Path: name, Err: windows.ERROR_FILE_NOT_FOUND}
|
||||
}
|
||||
r, e := openSequential(name, flag|windows.O_CLOEXEC)
|
||||
if e != nil {
|
||||
return nil, &os.PathError{Op: "open", Path: name, Err: e}
|
||||
}
|
||||
return os.NewFile(uintptr(r), name), nil
|
||||
}
|
||||
|
||||
func makeInheritSa() *windows.SecurityAttributes {
|
||||
var sa windows.SecurityAttributes
|
||||
sa.Length = uint32(unsafe.Sizeof(sa))
|
||||
sa.InheritHandle = 1
|
||||
return &sa
|
||||
}
|
||||
|
||||
func openSequential(path string, mode int) (fd windows.Handle, err error) {
|
||||
if len(path) == 0 {
|
||||
return windows.InvalidHandle, windows.ERROR_FILE_NOT_FOUND
|
||||
}
|
||||
pathp, err := windows.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return windows.InvalidHandle, err
|
||||
}
|
||||
var access uint32
|
||||
switch mode & (windows.O_RDONLY | windows.O_WRONLY | windows.O_RDWR) {
|
||||
case windows.O_RDONLY:
|
||||
access = windows.GENERIC_READ
|
||||
case windows.O_WRONLY:
|
||||
access = windows.GENERIC_WRITE
|
||||
case windows.O_RDWR:
|
||||
access = windows.GENERIC_READ | windows.GENERIC_WRITE
|
||||
}
|
||||
if mode&windows.O_CREAT != 0 {
|
||||
access |= windows.GENERIC_WRITE
|
||||
}
|
||||
if mode&windows.O_APPEND != 0 {
|
||||
access &^= windows.GENERIC_WRITE
|
||||
access |= windows.FILE_APPEND_DATA
|
||||
}
|
||||
sharemode := uint32(windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE)
|
||||
var sa *windows.SecurityAttributes
|
||||
if mode&windows.O_CLOEXEC == 0 {
|
||||
sa = makeInheritSa()
|
||||
}
|
||||
var createmode uint32
|
||||
switch {
|
||||
case mode&(windows.O_CREAT|windows.O_EXCL) == (windows.O_CREAT | windows.O_EXCL):
|
||||
createmode = windows.CREATE_NEW
|
||||
case mode&(windows.O_CREAT|windows.O_TRUNC) == (windows.O_CREAT | windows.O_TRUNC):
|
||||
createmode = windows.CREATE_ALWAYS
|
||||
case mode&windows.O_CREAT == windows.O_CREAT:
|
||||
createmode = windows.OPEN_ALWAYS
|
||||
case mode&windows.O_TRUNC == windows.O_TRUNC:
|
||||
createmode = windows.TRUNCATE_EXISTING
|
||||
default:
|
||||
createmode = windows.OPEN_EXISTING
|
||||
}
|
||||
// Use FILE_FLAG_SEQUENTIAL_SCAN rather than FILE_ATTRIBUTE_NORMAL as implemented in golang.
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#FILE_FLAG_SEQUENTIAL_SCAN
|
||||
h, e := windows.CreateFile(pathp, access, sharemode, sa, createmode, windows.FILE_FLAG_SEQUENTIAL_SCAN, 0)
|
||||
return h, e
|
||||
}
|
||||
+12
-5
@@ -35,10 +35,6 @@ type middleware struct {
|
||||
semconv semconv.HTTPServer
|
||||
}
|
||||
|
||||
func defaultHandlerFormatter(operation string, _ *http.Request) string {
|
||||
return operation
|
||||
}
|
||||
|
||||
// NewHandler wraps the passed handler in a span named after the operation and
|
||||
// enriches it with metrics.
|
||||
func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler {
|
||||
@@ -55,12 +51,17 @@ func NewMiddleware(operation string, opts ...Option) func(http.Handler) http.Han
|
||||
|
||||
defaultOpts := []Option{
|
||||
WithSpanOptions(trace.WithSpanKind(trace.SpanKindServer)),
|
||||
WithSpanNameFormatter(defaultHandlerFormatter),
|
||||
}
|
||||
|
||||
c := newConfig(append(defaultOpts, opts...)...)
|
||||
h.configure(c)
|
||||
|
||||
if h.spanNameFormatter == nil {
|
||||
h.spanNameFormatter = func(_ string, r *http.Request) string {
|
||||
return h.semconv.SpanName(r)
|
||||
}
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h.serveHTTP(w, r, next)
|
||||
@@ -138,7 +139,13 @@ func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http
|
||||
// ReadCloser fulfills a certain interface and it is indeed nil or NoBody.
|
||||
bw := request.NewBodyWrapper(r.Body, readRecordFunc)
|
||||
if r.Body != nil && r.Body != http.NoBody {
|
||||
origReq := r
|
||||
prevBody := r.Body
|
||||
r.Body = bw
|
||||
|
||||
// Restore the original body after the request is processed to avoid issues
|
||||
// with extra wrapper since `http/server.go` later checks type of `r.Body`.
|
||||
defer func() { origReq.Body = prevBody }()
|
||||
}
|
||||
|
||||
writeRecordFunc := func(int64) {}
|
||||
|
||||
Generated
Vendored
+41
-4
@@ -12,6 +12,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -19,8 +20,8 @@ import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0/httpconv"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0/httpconv"
|
||||
)
|
||||
|
||||
type HTTPClient struct {
|
||||
@@ -166,7 +167,7 @@ func (n HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue
|
||||
|
||||
func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) {
|
||||
if method == "" {
|
||||
return semconv.HTTPRequestMethodGet, attribute.KeyValue{}
|
||||
return semconv.HTTPRequestMethodOther, attribute.KeyValue{}
|
||||
}
|
||||
if attr, ok := methodLookup[method]; ok {
|
||||
return attr, attribute.KeyValue{}
|
||||
@@ -176,7 +177,7 @@ func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValu
|
||||
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
|
||||
return attr, orig
|
||||
}
|
||||
return semconv.HTTPRequestMethodGet, orig
|
||||
return semconv.HTTPRequestMethodOther, orig
|
||||
}
|
||||
|
||||
func (n HTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
|
||||
@@ -249,6 +250,9 @@ func (o MetricOpts) AddOptions() metric.AddOption {
|
||||
|
||||
func (n HTTPClient) MetricOptions(ma MetricAttributes) MetricOpts {
|
||||
attributes := n.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes)
|
||||
if ma.StatusCode == 0 && ma.Err != nil {
|
||||
attributes = append(attributes, n.ErrorType(ma.Err))
|
||||
}
|
||||
set := metric.WithAttributeSet(attribute.NewSet(attributes...))
|
||||
|
||||
return MetricOpts{
|
||||
@@ -257,6 +261,39 @@ func (n HTTPClient) MetricOptions(ma MetricAttributes) MetricOpts {
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorType returns an error.type attribute for the given error. The otelhttp
|
||||
// Transport calls the underlying RoundTripper directly, so transport failures
|
||||
// arrive as *net.OpError (connection refused, timeout, etc.) rather than
|
||||
// *url.Error (which http.Client.Do adds above the Transport layer). This
|
||||
// function intentionally does not unwrap further: reporting a single concrete
|
||||
// type per failure keeps attribute cardinality bounded, as required by the
|
||||
// OTel spec (error.type SHOULD have low cardinality). Callers that need
|
||||
// finer-grained error distinctions should inspect the error themselves.
|
||||
func (n HTTPClient) ErrorType(err error) attribute.KeyValue {
|
||||
t := reflect.TypeOf(err)
|
||||
if t == nil {
|
||||
return semconv.ErrorTypeOther
|
||||
}
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
var value string
|
||||
if t.PkgPath() == "" || t.Name() == "" {
|
||||
// t.PkgPath() == "" covers builtin and unnamed types.
|
||||
// t.Name() == "" covers anonymous struct types that implement error,
|
||||
// which are uncommon but possible. Fall back to t.String() for both.
|
||||
value = t.String()
|
||||
} else {
|
||||
value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
|
||||
}
|
||||
|
||||
if value == "" {
|
||||
return semconv.ErrorTypeOther
|
||||
}
|
||||
|
||||
return semconv.ErrorTypeKey.String(value)
|
||||
}
|
||||
|
||||
func (n HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts MetricOpts) {
|
||||
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
|
||||
defer func() {
|
||||
|
||||
Generated
Vendored
+28
-11
@@ -20,8 +20,8 @@ import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0/httpconv"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0/httpconv"
|
||||
)
|
||||
|
||||
type RequestTraceAttrsOpts struct {
|
||||
@@ -243,6 +243,7 @@ type MetricAttributes struct {
|
||||
StatusCode int
|
||||
Route string
|
||||
AdditionalAttributes []attribute.KeyValue
|
||||
Err error
|
||||
}
|
||||
|
||||
type MetricData struct {
|
||||
@@ -250,13 +251,11 @@ type MetricData struct {
|
||||
RequestDuration time.Duration
|
||||
}
|
||||
|
||||
var (
|
||||
metricRecordOptionPool = &sync.Pool{
|
||||
New: func() any {
|
||||
return &[]metric.RecordOption{}
|
||||
},
|
||||
}
|
||||
)
|
||||
var metricRecordOptionPool = &sync.Pool{
|
||||
New: func() any {
|
||||
return &[]metric.RecordOption{}
|
||||
},
|
||||
}
|
||||
|
||||
func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
|
||||
attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes)
|
||||
@@ -270,9 +269,27 @@ func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
|
||||
metricRecordOptionPool.Put(recordOpts)
|
||||
}
|
||||
|
||||
// SpanName returns the span name for an HTTP request following the
|
||||
// OpenTelemetry HTTP semantic conventions.
|
||||
// It returns "{method} {route}" when the request has a pattern,
|
||||
// or just "{method}" when no route is available.
|
||||
// Non-standard HTTP methods are replaced by "HTTP".
|
||||
func (n HTTPServer) SpanName(r *http.Request) string {
|
||||
method := strings.ToUpper(r.Method)
|
||||
if _, ok := methodLookup[method]; !ok {
|
||||
method = "HTTP"
|
||||
}
|
||||
|
||||
route := httpRoute(r.Pattern)
|
||||
if route != "" {
|
||||
return method + " " + route
|
||||
}
|
||||
return method
|
||||
}
|
||||
|
||||
func (n HTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) {
|
||||
if method == "" {
|
||||
return semconv.HTTPRequestMethodGet, attribute.KeyValue{}
|
||||
return semconv.HTTPRequestMethodOther, attribute.KeyValue{}
|
||||
}
|
||||
if attr, ok := methodLookup[method]; ok {
|
||||
return attr, attribute.KeyValue{}
|
||||
@@ -282,7 +299,7 @@ func (n HTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValu
|
||||
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
|
||||
return attr, orig
|
||||
}
|
||||
return semconv.HTTPRequestMethodGet, orig
|
||||
return semconv.HTTPRequestMethodOther, orig
|
||||
}
|
||||
|
||||
func (n HTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
semconvNew "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
semconvNew "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
)
|
||||
|
||||
// SplitHostPort splits a network address hostport of the form "host",
|
||||
|
||||
Generated
Vendored
+2
-1
@@ -15,7 +15,7 @@ import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
otelsemconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
otelsemconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
|
||||
@@ -161,6 +161,7 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
t.semconv.MetricOptions(semconv.MetricAttributes{
|
||||
Req: r,
|
||||
StatusCode: statusCode,
|
||||
Err: err,
|
||||
AdditionalAttributes: append(labeler.Get(), t.metricAttributesFromRequest(r)...),
|
||||
}),
|
||||
)
|
||||
|
||||
+1
-1
@@ -4,4 +4,4 @@
|
||||
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
|
||||
// Version is the current release version of the otelhttp instrumentation.
|
||||
const Version = "0.68.0"
|
||||
const Version = "0.69.0"
|
||||
|
||||
+11
-7
@@ -96,9 +96,9 @@ linters:
|
||||
- "!**/exporters/zipkin/**"
|
||||
deny:
|
||||
- pkg: go.opentelemetry.io/otel/semconv
|
||||
desc: "Use go.opentelemetry.io/otel/semconv/v1.40.0 instead. If a newer semconv version has been released, update the depguard rule."
|
||||
desc: "Use go.opentelemetry.io/otel/semconv/v1.41.0 instead. If a newer semconv version has been released, update the depguard rule."
|
||||
allow:
|
||||
- go.opentelemetry.io/otel/semconv/v1.40.0
|
||||
- go.opentelemetry.io/otel/semconv/v1.41.0
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- appendAssign
|
||||
@@ -134,13 +134,16 @@ linters:
|
||||
strconcat: true
|
||||
revive:
|
||||
confidence: 0.01
|
||||
enable-all-rules: false
|
||||
enable-default-rules: true
|
||||
max-open-files: 2048
|
||||
rules:
|
||||
- name: blank-imports
|
||||
- name: bool-literal-in-expr
|
||||
- name: constant-logical-expr
|
||||
- name: context-as-argument
|
||||
arguments:
|
||||
- allowTypesBefore: '*testing.T'
|
||||
- allow-types-before: '*testing.T'
|
||||
disabled: true
|
||||
- name: context-keys-type
|
||||
- name: deep-exit
|
||||
@@ -152,7 +155,7 @@ linters:
|
||||
- name: duplicated-imports
|
||||
- name: early-return
|
||||
arguments:
|
||||
- preserveScope
|
||||
- preserve-scope
|
||||
- name: empty-block
|
||||
- name: empty-lines
|
||||
- name: error-naming
|
||||
@@ -161,7 +164,7 @@ linters:
|
||||
- name: errorf
|
||||
- name: exported
|
||||
arguments:
|
||||
- sayRepetitiveInsteadOfStutters
|
||||
- say-repetitive-instead-of-stutters
|
||||
- name: flag-parameter
|
||||
- name: identical-branches
|
||||
- name: if-return
|
||||
@@ -169,11 +172,12 @@ linters:
|
||||
- name: increment-decrement
|
||||
- name: indent-error-flow
|
||||
arguments:
|
||||
- preserveScope
|
||||
- preserve-scope
|
||||
- name: package-comments
|
||||
- name: range
|
||||
- name: range-val-in-closure
|
||||
- name: range-val-address
|
||||
- name: receiver-naming
|
||||
- name: redefines-builtin-id
|
||||
- name: string-format
|
||||
arguments:
|
||||
@@ -183,7 +187,7 @@ linters:
|
||||
- name: struct-tag
|
||||
- name: superfluous-else
|
||||
arguments:
|
||||
- preserveScope
|
||||
- preserve-scope
|
||||
- name: time-equal
|
||||
- name: unconditional-recursion
|
||||
- name: unexported-return
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
# Agent Guide for opentelemetry-go
|
||||
|
||||
This file contains active, task-oriented instructions for autonomous and semi-autonomous coding agents working in this repository.
|
||||
|
||||
Before starting any task, read `.github/copilot-instructions.md`, `CONTRIBUTING.md`, and this file.
|
||||
Treat `.github/copilot-instructions.md` as global passive guidance for every task, including docs-only and review-only work.
|
||||
|
||||
## Core expectations
|
||||
|
||||
- Preserve OpenTelemetry specification compliance, API stability, and idiomatic Go.
|
||||
- Prefer minimal, surgical changes over broad refactors or speculative cleanup.
|
||||
- Read the package you are editing and match its existing naming, option types, error handling, comments, tests, and concurrency patterns.
|
||||
- Keep public APIs backward compatible unless the task explicitly requires a breaking change.
|
||||
- Keep telemetry resilient and loosely coupled. Do not introduce behavior that can unexpectedly interfere with host applications.
|
||||
- Inspect boundaries carefully: input validation, resource limits, cancellation, shutdown, error propagation, concurrency, and memory growth.
|
||||
- Prefer fail-safe behavior and explicit invariants over implicit assumptions.
|
||||
- Keep dependencies minimal and justified.
|
||||
- Preserve host-application safety: telemetry should not panic, block indefinitely, or amplify attacker-controlled input.
|
||||
- Be conservative on hot paths. Avoid unnecessary allocations, reflection, interface churn, blocking, global state, and high-cardinality telemetry.
|
||||
- Write comments only for intent, invariants, and non-obvious constraints. Do not add comments that restate the code.
|
||||
|
||||
## Default workflow
|
||||
|
||||
For new features and behavior changes, use this order unless the task explicitly says otherwise:
|
||||
|
||||
1. Read the relevant package, its tests, and any package docs or `README.md`.
|
||||
2. Add or update a failing unit test that captures the required behavior or regression.
|
||||
3. Implement the smallest change that makes the test pass.
|
||||
4. Refactor only after the behavior is locked in, and only if the refactor keeps the diff focused.
|
||||
5. If the changed code is on a hot path or performance-sensitive, inspect existing benchmarks and run them. Add a benchmark if coverage is missing.
|
||||
6. Update documentation artifacts as needed while the context is fresh. Follow the documentation and changelog conventions below for the specific updates required.
|
||||
7. Run `make precommit` each time before considering the work complete.
|
||||
|
||||
For docs-only, test-only, or review-only tasks, still start with the required repository guidance above, then skip the workflow steps that do not apply while keeping the same discipline around scope, verification, and repository conventions.
|
||||
|
||||
## Verification
|
||||
|
||||
- Use `make` as the canonical repository verification command. The default target is `precommit`.
|
||||
- `make precommit` is the expected final verification step for linting, generation, README checks, module checks, and tests.
|
||||
- During iteration, targeted commands are fine for fast feedback, but do not stop there if the task changes code.
|
||||
- If you touch performance-sensitive code, run focused benchmarks and compare the results using `benchstat` in addition to `make`.
|
||||
|
||||
## Documentation and changelog
|
||||
|
||||
- Non-internal, non-test packages should have Go doc comments, usually in `doc.go`.
|
||||
- Non-internal, non-test, non-documentation packages should also have a `README.md` with at least a title and a `pkg.go.dev` badge.
|
||||
- Prefer examples over long code snippets in GoDoc when practical.
|
||||
- Keep docs aligned with actual behavior. Do not leave stale comments, stale examples, or stale package documentation behind.
|
||||
- For user-visible changes, update `CHANGELOG.md` under the appropriate `Added`, `Changed`, `Deprecated`, `Fixed`, or `Removed` section within `## [Unreleased]`.
|
||||
|
||||
## Repository habits
|
||||
|
||||
- Prefer focused diffs. Avoid drive-by cleanup.
|
||||
- Follow existing option patterns and exported API conventions instead of inventing new abstractions.
|
||||
- Generated files are checked in. If your change affects generation, keep generated output up to date.
|
||||
- Prefer fast local search tools such as `rg` when exploring the repository.
|
||||
- When changing behavior, make the invariants explicit in tests.
|
||||
|
||||
## Personas
|
||||
|
||||
### Feature Agent
|
||||
|
||||
Use this persona for new behavior, new API surface, or spec-driven feature work.
|
||||
|
||||
- Start with a failing unit test.
|
||||
- Confirm the expected behavior against the spec, existing package behavior, and public API compatibility.
|
||||
- Implement the smallest viable change.
|
||||
- Update GoDoc, examples, `README.md`, and `CHANGELOG.md` when the change is user-visible.
|
||||
- If the feature touches a hot path, check benchmarks and add one if the coverage is missing.
|
||||
|
||||
### Refactoring Agent
|
||||
|
||||
Use this persona when improving structure without intentionally changing behavior.
|
||||
|
||||
- Treat behavior preservation as the default contract.
|
||||
- Add or tighten tests before moving code if current behavior is not already pinned down.
|
||||
- Avoid broad rewrites, clever abstractions, or package-wide cleanup unless explicitly requested.
|
||||
- If a refactor touches a hot path, benchmark before and after.
|
||||
- Keep API shape, semantics, concurrency guarantees, and failure modes unchanged unless the task says otherwise.
|
||||
|
||||
### Test Agent
|
||||
|
||||
Use this persona when adding missing coverage, reproducing bugs, or hardening regressions.
|
||||
|
||||
- Reproduce the bug or missing behavior with the smallest failing test you can.
|
||||
- Prefer testing public behavior and externally visible invariants.
|
||||
- Add targeted regression tests before changing production code.
|
||||
- Only change production code when it is required to make the tested behavior correct or testable.
|
||||
- Keep tests deterministic, readable, and aligned with package patterns.
|
||||
|
||||
### Performance Agent
|
||||
|
||||
Use this persona for hot-path work, allocation reduction, or throughput and latency improvements.
|
||||
|
||||
- Benchmark first to establish a baseline.
|
||||
- Prefer changes that reduce allocations, copying, interface churn, and unnecessary synchronization.
|
||||
- Do not trade away correctness, spec compliance, or API stability for micro-optimizations.
|
||||
- Add or update benchmarks when performance-sensitive coverage is missing.
|
||||
- If you materially change a hot path, capture before-and-after results, preferably with `benchstat`.
|
||||
|
||||
### Review Agent
|
||||
|
||||
Use this persona when asked to review code, patches, or pull requests.
|
||||
|
||||
- Lead with findings, not summaries.
|
||||
- Order findings by severity and include precise file and line references when available.
|
||||
- Focus on correctness, spec compliance, API compatibility, concurrency safety, resilience, performance regressions, missing tests, missing benchmarks, documentation gaps, and changelog gaps.
|
||||
- Call out when a diff is broader than necessary.
|
||||
- If you find no issues, say that explicitly and note any residual risks or verification gaps.
|
||||
+96
-1
@@ -11,6 +11,100 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
<!-- Released section -->
|
||||
<!-- Don't change this section unless doing release -->
|
||||
|
||||
## [1.44.0/0.66.0/0.20.0/0.0.17] 2026-05-27
|
||||
|
||||
### Added
|
||||
|
||||
- Add `ByteSlice` and `ByteSliceValue` functions for new `BYTESLICE` attribute type in `go.opentelemetry.io/otel/attribute`. (#7948)
|
||||
- Apply attribute value limit to the `KindBytes` attribute type in `go.opentelemetry.io/otel/sdk/log`. (#7990)
|
||||
- Apply attribute value limit to the `BYTESLICE` attribute type in `go.opentelemetry.io/otel/sdk/trace`. (#7990)
|
||||
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/trace`. (#8153)
|
||||
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlptrace`. (#8153)
|
||||
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlplog`. (#8153)
|
||||
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric`. (#8153)
|
||||
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/exporters/zipkin`. (#8153)
|
||||
- Add `String` method for `Value` type in `go.opentelemetry.io/otel/attribute`. (#8142)
|
||||
- Add `Slice` and `SliceValue` functions for new `SLICE` attribute type in `go.opentelemetry.io/otel/attribute`. (#8166)
|
||||
- Support `SLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlptrace`. (#8216)
|
||||
- Support `SLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlplog`. (#8216)
|
||||
- Support `SLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric`. (#8216)
|
||||
- Support `SLICE` attributes in `go.opentelemetry.io/otel/exporters/zipkin`. (#8216)
|
||||
- Apply `AttributeValueLengthLimit` to `attribute.SLICE` type attribute values in `go.opentelemetry.io/otel/sdk/trace`, recursively truncating contained string values. (#8217)
|
||||
- Add `Error` field on `Record` type in `go.opentelemetry.io/otel/log/logtest`. (#8148)
|
||||
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`. (#8157)
|
||||
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#8157)
|
||||
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`. (#8157)
|
||||
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#8157)
|
||||
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`. (#8157)
|
||||
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8157)
|
||||
- Add `Settable` to `go.opentelemetry.io/otel/metric/x` to allow reusing attribute options. (#8178)
|
||||
- Add experimental support for splitting metric data across multiple batches in `go.opentelemetry.io/otel/sdk/metric`.
|
||||
Set `OTEL_GO_X_METRIC_EXPORT_BATCH_SIZE=<max_size>` to enable for all periodic readers.
|
||||
See `go.opentelemetry.io/otel/sdk/metric/internal/x` for feature documentation. (#8071)
|
||||
- Add experimental self-observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`.
|
||||
Enable with `OTEL_GO_X_SELF_OBSERVABILITY=true` environment variable.
|
||||
See `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/x` for feature documentation. (#8192)
|
||||
- Add experimental self-observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`.
|
||||
Enable with `OTEL_GO_X_SELF_OBSERVABILITY=true` environment variable.
|
||||
See `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/x` for feature documentation. (#8194)
|
||||
- Add experimental self-observability metrics in `go.opentelemetry.io/otel/exporters/stdout/stdoutlog`.
|
||||
Enable with `OTEL_GO_X_SELF_OBSERVABILITY=true` environment variable.
|
||||
See `go.opentelemetry.io/otel/stdout/stdoutlog/internal/x` for feature documentation. (#8263)
|
||||
- Add `WithDefaultAttributes` to `go.opentelemetry.io/otel/metric/x` to support setting default attributes on instruments. (#8135)
|
||||
- Add `go.opentelemetry.io/otel/semconv/v1.41.0` package.
|
||||
The package contains semantic conventions from the `v1.41.0` version of the OpenTelemetry Semantic Conventions.
|
||||
See the [migration documentation](./semconv/v1.41.0/MIGRATION.md) for information on how to upgrade from `go.opentelemetry.io/otel/semconv/v1.40.0`. (#8324)
|
||||
- Add Observable variants of instruments to `go.opentelemetry.io/otel/semconv/v1.41.0` package. (#8350)
|
||||
- Generate explicit histogram bucket boundaries from weaver configuration for HTTP and RPC duration instruments in `go.opentelemetry.io/otel/semconv/v1.41.0`. (#8002)
|
||||
|
||||
### Changed
|
||||
|
||||
- ⚠️ **Breaking Change:** `go.opentelemetry.io/otel/sdk/metric` now applies a default cardinality limit of 2000 to comply with the Metrics SDK specification recommendation.
|
||||
New attribute sets are dropped when the cardinality limit is reached. The measurement of these sets are aggregated into a special attribute set containing `attribute.Bool("otel.metric.overflow", true)`.
|
||||
This can break users who relied on the previous unlimited default.
|
||||
Set `WithCardinalityLimit(0)` or the deprecated `OTEL_GO_X_CARDINALITY_LIMIT=0` environment variable to preserve unlimited cardinality.
|
||||
Note that support for `OTEL_GO_X_CARDINALITY_LIMIT` may be removed in a future release. (#8247)
|
||||
- `ErrorType` in `go.opentelemetry.io/otel/semconv` now unwraps errors created with `fmt.Errorf` when deriving the `error.type` attribute. (#8133)
|
||||
- `go.opentelemetry.io/otel/sdk/log` now unwraps error chains created with `fmt.Errorf` when deriving the `error.type` attribute from errors on log records. (#8133)
|
||||
- `Set.MarshalLog` method in `go.opentelemetry.io/otel/attribute` now uses `Value.String` formatting following the [OpenTelemetry AnyValue representation for non-OTLP protocols](https://opentelemetry.io/docs/specs/otel/common/#anyvalue). (#8169)
|
||||
- Optimize `go.opentelemetry.io/otel/sdk/metric` to return a drop reservoir and short-circuit `Offer` calls to the exemplar reservoir when `exemplar.AlwaysOffFilter` is configured. (#8211) (#8267)
|
||||
- Optimize `go.opentelemetry.io/otel/sdk/metric` to return a drop reservoir for asynchronous instruments when `exemplar.TraceBasedFilter` is configured. (#8286)
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Deprecate `Value.Emit` method in `go.opentelemetry.io/otel/attribute`.
|
||||
Use `Value.String` instead. (#8176)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`.
|
||||
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
|
||||
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`.
|
||||
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
|
||||
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`.
|
||||
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
|
||||
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`.
|
||||
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
|
||||
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`.
|
||||
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
|
||||
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`.
|
||||
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
|
||||
- Fix gzipped request body replay on redirect in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#8135)
|
||||
- Fix gzipped request body replay on redirect in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8152)
|
||||
- `go.opentelemetry.io/otel/exporters/prometheus` now uses `Value.String` formatting for label values following the [OpenTelemetry AnyValue representation for non-OTLP protocols](https://opentelemetry.io/docs/specs/otel/common/#anyvalue). (#8170)
|
||||
- Propagate errors from the exporter when calling `Shutdown` on `BatchSpanProcessor` in `go.opentelemetry.io/otel/sdk/trace`. (#8197)
|
||||
- Fix stale status code reporting on self-observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp` and `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8226)
|
||||
- Fix a concurrent `Collect` data race and potential panic in `go.opentelemetry.io/otel/exporters/prometheus` when `WithResourceAsConstantLabels` option is used. (#8227)
|
||||
- Fix race condition in `FixedSizeReservoir` in `go.opentelemetry.io/otel/sdk/metric/exemplar` by reverting #7447. (#8249)
|
||||
- Fix `FixedSizeReservoir` in `go.opentelemetry.io/otel/sdk/metric/exemplar` to safely handle zero size.
|
||||
A capacity check in the constructor initializes the reservoir safely and skips initialization for zero-cap; early returns in `Offer()` and `Collect()` ensure no-op behavior. (#8295)
|
||||
- Fix counting of spans and logs in self-observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`, `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`, `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`, and `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8254)
|
||||
- Drop conflicting scope attributes named `name`, `version`, or `schema_url` from metric labels in `go.opentelemetry.io/otel/exporters/prometheus`, preserving the dedicated `otel_scope_name`, `otel_scope_version`, and `otel_scope_schema_url` labels. (#8264)
|
||||
- Close schema files opened by `ParseFile` in `go.opentelemetry.io/otel/schema/v1.0` and `go.opentelemetry.io/otel/schema/v1.1`. ([GHSA-995v-fvrw-c78m](https://github.com/open-telemetry/opentelemetry-go/security/advisories/GHSA-995v-fvrw-c78m))
|
||||
- Enforce the 8192-byte baggage size limit during extraction/parsing, changing behavior when the limit is exceeded in `go.opentelemetry.io/otel/baggage` and `go.opentelemetry.io/otel/propagation`. (#8222)
|
||||
- Fix `go.opentelemetry.io/otel/semconv/v1.41.0` to include `Attr*` helper methods for required attributes on observable instruments. (#8361)
|
||||
- Limit baggage extraction error reporting in `go.opentelemetry.io/otel/propagation` to prevent malformed or oversized baggage headers from flooding logs. ([GHSA-5wrp-cwcj-q835](https://github.com/open-telemetry/opentelemetry-go/security/advisories/GHSA-5wrp-cwcj-q835))
|
||||
|
||||
## [1.43.0/0.65.0/0.19.0] 2026-04-02
|
||||
|
||||
### Added
|
||||
@@ -3619,7 +3713,8 @@ It contains api and sdk for trace and meter.
|
||||
- CircleCI build CI manifest files.
|
||||
- CODEOWNERS file to track owners of this project.
|
||||
|
||||
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.43.0...HEAD
|
||||
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.44.0...HEAD
|
||||
[1.44.0/0.66.0/0.20.0/0.0.17]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.44.0
|
||||
[1.43.0/0.65.0/0.19.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.43.0
|
||||
[1.42.0/0.64.0/0.18.0/0.0.16]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.42.0
|
||||
[1.41.0/0.63.0/0.17.0/0.0.15]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.41.0
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# Instructions for Claude Code
|
||||
|
||||
@AGENTS.md
|
||||
+89
-12
@@ -11,6 +11,12 @@ for a summary description of past meetings. To request edit access,
|
||||
join the meeting or get in touch on
|
||||
[Slack](https://cloud-native.slack.com/archives/C01NPAXACKT).
|
||||
|
||||
The meeting is open for all to join. We invite everyone to join our
|
||||
meeting, regardless of your experience level. Whether you're a
|
||||
seasoned OpenTelemetry developer, just starting your journey, or
|
||||
simply curious about the work we do, you're more than welcome to
|
||||
participate!
|
||||
|
||||
## Development
|
||||
|
||||
You can view and edit the source code by cloning this repository:
|
||||
@@ -746,8 +752,8 @@ Encapsulate setup in constructor functions, ensuring clear ownership and scope:
|
||||
import (
|
||||
"errors"
|
||||
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0/otelconv"
|
||||
)
|
||||
|
||||
type SDKComponent struct {
|
||||
@@ -808,11 +814,11 @@ func (c *Component) initObservability() {
|
||||
|
||||
#### Performance
|
||||
|
||||
When observability is disabled there should be little to no overhead.
|
||||
When observability is disabled or the instrument is not `Enabled`, there should be little to no overhead.
|
||||
|
||||
```go
|
||||
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) error {
|
||||
if e.inst != nil {
|
||||
if e.inst != nil && e.inst.Enabled(ctx) {
|
||||
attrs := expensiveOperation()
|
||||
e.inst.recordSpanInflight(ctx, int64(len(spans)), attrs...)
|
||||
}
|
||||
@@ -829,7 +835,7 @@ func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan)
|
||||
}
|
||||
|
||||
func (i *instrumentation) recordSpanInflight(ctx context.Context, count int64, attrs ...attribute.KeyValue) {
|
||||
if i == nil || i.inflight == nil {
|
||||
if i == nil || i.inflight == nil || !i.inflight.Enabled(ctx) {
|
||||
return
|
||||
}
|
||||
i.inflight.Add(ctx, count, metric.WithAttributes(attrs...))
|
||||
@@ -865,8 +871,12 @@ var (
|
||||
)
|
||||
|
||||
func (i *instrumentation) record(ctx context.Context, value int64, baseAttrs ...attribute.KeyValue) {
|
||||
if !i.counter.Enabled(ctx) {
|
||||
return
|
||||
}
|
||||
attrs := attrPool.Get().(*[]attribute.KeyValue)
|
||||
defer func() {
|
||||
clear(*attrs) // Clear references to strings/etc to let GC collect them.
|
||||
*attrs = (*attrs)[:0] // Reset.
|
||||
attrPool.Put(attrs)
|
||||
}()
|
||||
@@ -877,6 +887,7 @@ func (i *instrumentation) record(ctx context.Context, value int64, baseAttrs ...
|
||||
|
||||
addOpt := addOptPool.Get().(*[]metric.AddOption)
|
||||
defer func() {
|
||||
clear(*addOpt)
|
||||
*addOpt = (*addOpt)[:0]
|
||||
addOptPool.Put(addOpt)
|
||||
}()
|
||||
@@ -1007,16 +1018,20 @@ Ensure observability measurements receive the correct context, especially for tr
|
||||
```go
|
||||
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) error {
|
||||
// Use the provided context for observability measurements
|
||||
e.inst.recordSpanExportStarted(ctx, len(spans))
|
||||
if e.inst.Enabled(ctx) {
|
||||
e.inst.recordSpanExportStarted(ctx, len(spans))
|
||||
}
|
||||
|
||||
err := e.doExport(ctx, spans)
|
||||
|
||||
if err != nil {
|
||||
e.inst.recordSpanExportFailed(ctx, len(spans), err)
|
||||
} else {
|
||||
e.inst.recordSpanExportSucceeded(ctx, len(spans))
|
||||
if e.inst.Enabled(ctx) {
|
||||
if err != nil {
|
||||
e.inst.recordSpanExportFailed(ctx, len(spans), err)
|
||||
} else {
|
||||
e.inst.recordSpanExportSucceeded(ctx, len(spans))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return err
|
||||
}
|
||||
```
|
||||
@@ -1039,7 +1054,7 @@ func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan)
|
||||
|
||||
All observability metrics should follow the [OpenTelemetry Semantic Conventions for SDK metrics](https://github.com/open-telemetry/semantic-conventions/blob/1cf2476ae5e518225a766990a28a6d5602bd5a30/docs/otel/sdk-metrics.md).
|
||||
|
||||
Use the metric semantic conventions convenience package [otelconv](./semconv/v1.40.0/otelconv/metric.go).
|
||||
Use the metric semantic conventions convenience package [otelconv](./semconv/v1.41.0/otelconv/metric.go).
|
||||
|
||||
##### Component Identification
|
||||
|
||||
@@ -1109,6 +1124,68 @@ func TestObservability(t *testing.T) {
|
||||
Test order should not affect results.
|
||||
Ensure that any global state (e.g. component ID counters) is reset between tests.
|
||||
|
||||
### Experimental Features
|
||||
|
||||
To support the development of new features in the specification, we use the following patterns to implement in-development features without adding new public artifacts in stable modules.
|
||||
|
||||
#### Experimental behavior with no API artifacts
|
||||
|
||||
Features that change behavior without changing the API (e.g., exemplar collection, auto-generation of identifiers) are implemented behind a feature gate.
|
||||
The implementation resides in an `/internal/x` package and is activated through environment variables with the `OTEL_GO_X_` prefix (e.g., `OTEL_GO_X_OBSERVABILITY`).
|
||||
The feature must be documented in a `README.md` file in the `/internal/x` package.
|
||||
|
||||
#### Experimental methods on SDK-only interfaces
|
||||
|
||||
Features that require new methods on SDK interfaces are defined as a new interface in an experimental module (e.g., `go.opentelemetry.io/otel/sdk/x`).
|
||||
The SDK uses type assertions (without importing the unstable package) to check if passing types implement these experimental interfaces.
|
||||
The SDK must not depend on the experimental module.
|
||||
|
||||
#### Experimental structs, functions, or interfaces
|
||||
|
||||
Features that don't need any changes to the existing stable package are implemented in an experimental module (e.g., `go.opentelemetry.io/otel/sdk/x`).
|
||||
|
||||
#### Experimental signals and components
|
||||
|
||||
New telemetry signals (e.g., Logs before stabilization) and components (e.g. bridges) are hosted in new, unstable modules (e.g., `go.opentelemetry.io/otel/log` before 1.0.0).
|
||||
The package should have the final name it will use once stabilized (i.e. not `/x`), and is released at a v0.x.y version to indicate it is not stable.
|
||||
Most new components are hosted in [opentelemetry-go-contrib](https://github.com/open-telemetry/opentelemetry-go-contrib).
|
||||
|
||||
#### Experimental options for API or SDK functions
|
||||
|
||||
Experimental Options functions are implemented in an experimental module (e.g., `go.opentelemetry.io/otel/sdk/x`).
|
||||
The return type of the Option function must embed the option's type (e.g. `metric.InstrumentOption`), and have an `Experimental()` method to prevent the API from panicking when the option is used.
|
||||
The SDK uses type assertions (without importing the unstable package) to check if passing types implement these experimental interfaces.
|
||||
The SDK must not depend on the experimental module.
|
||||
|
||||
For example:
|
||||
|
||||
```go
|
||||
type myOption struct {
|
||||
// Embed the stable option type.
|
||||
metric.InstrumentOption
|
||||
value string
|
||||
}
|
||||
|
||||
// Experimental prevents the API from panicking when the option is used.
|
||||
func (o myOption) Experimental() {}
|
||||
|
||||
// The SDK can use type assertions to use this function.
|
||||
func (o myOption) Value() string { return o.value }
|
||||
|
||||
func WithMyOption(value string) metric.InstrumentOption {
|
||||
return myOption{value: value}
|
||||
}
|
||||
```
|
||||
|
||||
#### Not Supported
|
||||
|
||||
The following kinds of experimental features are **not currently supported** on stable interfaces:
|
||||
|
||||
- Experimental methods on API interfaces
|
||||
- Experimental fields for API or SDK exported structs
|
||||
|
||||
In some cases forks or long-lived branches may be used for prototyping these features.
|
||||
|
||||
## Approvers and Maintainers
|
||||
|
||||
### Maintainers
|
||||
|
||||
+9
-1
@@ -191,8 +191,16 @@ benchmark: $(OTEL_GO_MOD_DIRS:%=benchmark/%)
|
||||
benchmark/%:
|
||||
cd $* && $(GO) test -run='^$$' -bench=. $(ARGS) ./...
|
||||
|
||||
# sdk/metric is split into two shards to work around CodSpeed limitations.
|
||||
# See https://github.com/CodSpeedHQ/codspeed-go/issues/56
|
||||
BENCHMARK_SHARDS := $(filter-out ./sdk/metric,$(OTEL_GO_MOD_DIRS)) ./sdk/metric/root ./sdk/metric/internal
|
||||
benchmark/./sdk/metric/root:
|
||||
cd ./sdk/metric && $(GO) test -run='^$$' -bench=. $(ARGS) . ./exemplar/...
|
||||
benchmark/./sdk/metric/internal:
|
||||
cd ./sdk/metric && $(GO) test -run='^$$' -bench=. $(ARGS) ./internal/...
|
||||
|
||||
print-sharded-benchmarks:
|
||||
@echo $(OTEL_GO_MOD_DIRS) | jq -cR 'split(" ")'
|
||||
@echo $(BENCHMARK_SHARDS) | jq -cR 'split(" ")'
|
||||
|
||||
.PHONY: golangci-lint golangci-lint-fix
|
||||
golangci-lint-fix: ARGS=--fix
|
||||
|
||||
+3
-1
@@ -105,7 +105,9 @@ func (d *defaultAttrEncoder) Encode(iter Iterator) string {
|
||||
if keyValue.Value.Type() == STRING {
|
||||
copyAndEscape(buf, keyValue.Value.AsString())
|
||||
} else {
|
||||
_, _ = buf.WriteString(keyValue.Value.Emit())
|
||||
_, _ = buf.WriteString(
|
||||
keyValue.Value.Emit(),
|
||||
) //nolint:staticcheck // Preserve the existing default encoder output.
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
|
||||
+47
-11
@@ -27,6 +27,8 @@ const (
|
||||
int64SliceID uint64 = 3762322556277578591 // "_[]int64" (little endian)
|
||||
float64SliceID uint64 = 7308324551835016539 // "[]double" (little endian)
|
||||
stringSliceID uint64 = 7453010373645655387 // "[]string" (little endian)
|
||||
byteSliceID uint64 = 6874028470941080415 // "_[]byte_" (little endian)
|
||||
sliceID uint64 = 7883494272577650031 // "__slice_" (little endian)
|
||||
emptyID uint64 = 7305809155345288421 // "__empty_" (little endian)
|
||||
)
|
||||
|
||||
@@ -42,53 +44,87 @@ func hashKVs(kvs []KeyValue) uint64 {
|
||||
// hashKV returns the xxHash64 hash of kv with h as the base.
|
||||
func hashKV(h xxhash.Hash, kv KeyValue) xxhash.Hash {
|
||||
h = h.String(string(kv.Key))
|
||||
return hashValue(h, kv.Value)
|
||||
}
|
||||
|
||||
switch kv.Value.Type() {
|
||||
func hashValue(h xxhash.Hash, v Value) xxhash.Hash {
|
||||
switch v.Type() {
|
||||
case BOOL:
|
||||
h = h.Uint64(boolID)
|
||||
h = h.Uint64(kv.Value.numeric)
|
||||
h = h.Uint64(v.numeric)
|
||||
case INT64:
|
||||
h = h.Uint64(int64ID)
|
||||
h = h.Uint64(kv.Value.numeric)
|
||||
h = h.Uint64(v.numeric)
|
||||
case FLOAT64:
|
||||
h = h.Uint64(float64ID)
|
||||
// Assumes numeric stored with math.Float64bits.
|
||||
h = h.Uint64(kv.Value.numeric)
|
||||
h = h.Uint64(v.numeric)
|
||||
case STRING:
|
||||
h = h.Uint64(stringID)
|
||||
h = h.String(kv.Value.stringly)
|
||||
h = h.String(v.stringly)
|
||||
case BOOLSLICE:
|
||||
h = h.Uint64(boolSliceID)
|
||||
rv := reflect.ValueOf(kv.Value.slice)
|
||||
rv := reflect.ValueOf(v.slice)
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
h = h.Bool(rv.Index(i).Bool())
|
||||
}
|
||||
case INT64SLICE:
|
||||
h = h.Uint64(int64SliceID)
|
||||
rv := reflect.ValueOf(kv.Value.slice)
|
||||
rv := reflect.ValueOf(v.slice)
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
h = h.Int64(rv.Index(i).Int())
|
||||
}
|
||||
case FLOAT64SLICE:
|
||||
h = h.Uint64(float64SliceID)
|
||||
rv := reflect.ValueOf(kv.Value.slice)
|
||||
rv := reflect.ValueOf(v.slice)
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
h = h.Float64(rv.Index(i).Float())
|
||||
}
|
||||
case STRINGSLICE:
|
||||
h = h.Uint64(stringSliceID)
|
||||
rv := reflect.ValueOf(kv.Value.slice)
|
||||
rv := reflect.ValueOf(v.slice)
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
h = h.String(rv.Index(i).String())
|
||||
}
|
||||
case BYTESLICE:
|
||||
h = h.Uint64(byteSliceID)
|
||||
h = h.String(v.stringly)
|
||||
case SLICE:
|
||||
h = h.Uint64(sliceID)
|
||||
switch vals := v.slice.(type) {
|
||||
case [0]Value:
|
||||
// No values to hash, but the type identifier is still hashed above.
|
||||
case [1]Value:
|
||||
h = hashValueSlice(h, vals[:])
|
||||
case [2]Value:
|
||||
h = hashValueSlice(h, vals[:])
|
||||
case [3]Value:
|
||||
h = hashValueSlice(h, vals[:])
|
||||
case [4]Value:
|
||||
h = hashValueSlice(h, vals[:])
|
||||
case [5]Value:
|
||||
h = hashValueSlice(h, vals[:])
|
||||
default:
|
||||
rv := reflect.ValueOf(v.slice)
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
h = hashValue(h, rv.Index(i).Interface().(Value))
|
||||
}
|
||||
}
|
||||
case EMPTY:
|
||||
h = h.Uint64(emptyID)
|
||||
default:
|
||||
// Logging is an alternative, but using the internal logger here
|
||||
// causes an import cycle so it is not done.
|
||||
v := kv.Value.AsInterface()
|
||||
msg := fmt.Sprintf("unknown value type: %[1]v (%[1]T)", v)
|
||||
val := v.AsInterface()
|
||||
msg := fmt.Sprintf("unknown value type: %[1]v (%[1]T)", val)
|
||||
panic(msg)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func hashValueSlice(h xxhash.Hash, vals []Value) xxhash.Hash {
|
||||
for _, v := range vals {
|
||||
h = hashValue(h, v)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
+22
@@ -117,6 +117,28 @@ func (k Key) StringSlice(v []string) KeyValue {
|
||||
}
|
||||
}
|
||||
|
||||
// ByteSlice creates a KeyValue instance with a BYTESLICE Value.
|
||||
//
|
||||
// If creating both a key and value at the same time, use the provided
|
||||
// convenience function instead -- ByteSlice(name, value).
|
||||
func (k Key) ByteSlice(v []byte) KeyValue {
|
||||
return KeyValue{
|
||||
Key: k,
|
||||
Value: ByteSliceValue(v),
|
||||
}
|
||||
}
|
||||
|
||||
// Slice creates a KeyValue instance with a SLICE Value.
|
||||
//
|
||||
// If creating both a key and value at the same time, use the provided
|
||||
// convenience function instead -- Slice(name, values...).
|
||||
func (k Key) Slice(v ...Value) KeyValue {
|
||||
return KeyValue{
|
||||
Key: k,
|
||||
Value: SliceValue(v...),
|
||||
}
|
||||
}
|
||||
|
||||
// Defined reports whether the key is not empty.
|
||||
func (k Key) Defined() bool {
|
||||
return len(k) != 0
|
||||
|
||||
+10
@@ -68,6 +68,16 @@ func StringSlice(k string, v []string) KeyValue {
|
||||
return Key(k).StringSlice(v)
|
||||
}
|
||||
|
||||
// ByteSlice creates a KeyValue with a BYTESLICE Value type.
|
||||
func ByteSlice(k string, v []byte) KeyValue {
|
||||
return Key(k).ByteSlice(v)
|
||||
}
|
||||
|
||||
// Slice creates a KeyValue with a SLICE Value type.
|
||||
func Slice(k string, v ...Value) KeyValue {
|
||||
return Key(k).Slice(v...)
|
||||
}
|
||||
|
||||
// Stringer creates a new key-value pair with a passed name and a string
|
||||
// value generated by the passed Stringer interface.
|
||||
func Stringer(k string, v fmt.Stringer) KeyValue {
|
||||
|
||||
+2
-2
@@ -401,7 +401,7 @@ func computeDataFixed(kvs []KeyValue) any {
|
||||
func computeDataReflect(kvs []KeyValue) any {
|
||||
at := reflect.New(reflect.ArrayOf(len(kvs), keyValueType)).Elem()
|
||||
for i, keyValue := range kvs {
|
||||
*(at.Index(i).Addr().Interface().(*KeyValue)) = keyValue
|
||||
*at.Index(i).Addr().Interface().(*KeyValue) = keyValue
|
||||
}
|
||||
return at.Interface()
|
||||
}
|
||||
@@ -415,7 +415,7 @@ func (l *Set) MarshalJSON() ([]byte, error) {
|
||||
func (l Set) MarshalLog() any {
|
||||
kvs := make(map[string]string)
|
||||
for _, kv := range l.ToSlice() {
|
||||
kvs[string(kv.Key)] = kv.Value.Emit()
|
||||
kvs[string(kv.Key)] = kv.Value.String()
|
||||
}
|
||||
return kvs
|
||||
}
|
||||
|
||||
+4
-2
@@ -17,11 +17,13 @@ func _() {
|
||||
_ = x[INT64SLICE-6]
|
||||
_ = x[FLOAT64SLICE-7]
|
||||
_ = x[STRINGSLICE-8]
|
||||
_ = x[BYTESLICE-9]
|
||||
_ = x[SLICE-10]
|
||||
}
|
||||
|
||||
const _Type_name = "EMPTYBOOLINT64FLOAT64STRINGBOOLSLICEINT64SLICEFLOAT64SLICESTRINGSLICE"
|
||||
const _Type_name = "EMPTYBOOLINT64FLOAT64STRINGBOOLSLICEINT64SLICEFLOAT64SLICESTRINGSLICEBYTESLICESLICE"
|
||||
|
||||
var _Type_index = [...]uint8{0, 5, 9, 14, 21, 27, 36, 46, 58, 69}
|
||||
var _Type_index = [...]uint8{0, 5, 9, 14, 21, 27, 36, 46, 58, 69, 78, 83}
|
||||
|
||||
func (i Type) String() string {
|
||||
idx := int(i) - 0
|
||||
|
||||
+742
@@ -4,9 +4,14 @@
|
||||
package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
attribute "go.opentelemetry.io/otel/attribute/internal"
|
||||
)
|
||||
@@ -45,6 +50,10 @@ const (
|
||||
FLOAT64SLICE
|
||||
// STRINGSLICE is a slice of strings Type Value.
|
||||
STRINGSLICE
|
||||
// BYTESLICE is a slice of bytes Type Value.
|
||||
BYTESLICE
|
||||
// SLICE is a slice of Value Type values.
|
||||
SLICE
|
||||
// INVALID is used for a Value with no value set.
|
||||
//
|
||||
// Deprecated: Use EMPTY instead as an empty value is a valid value.
|
||||
@@ -134,6 +143,19 @@ func StringSliceValue(v []string) Value {
|
||||
return Value{vtype: STRINGSLICE, slice: attribute.SliceValue(v)}
|
||||
}
|
||||
|
||||
// ByteSliceValue creates a BYTESLICE Value.
|
||||
func ByteSliceValue(v []byte) Value {
|
||||
return Value{
|
||||
vtype: BYTESLICE,
|
||||
stringly: string(v),
|
||||
}
|
||||
}
|
||||
|
||||
// SliceValue creates a SLICE Value.
|
||||
func SliceValue(v ...Value) Value {
|
||||
return Value{vtype: SLICE, slice: sliceValue(v)}
|
||||
}
|
||||
|
||||
// Type returns a type of the Value.
|
||||
func (v Value) Type() Type {
|
||||
return v.vtype
|
||||
@@ -215,6 +237,59 @@ func (v Value) asStringSlice() []string {
|
||||
return attribute.AsSlice[string](v.slice)
|
||||
}
|
||||
|
||||
// AsSlice returns the []Value value. Make sure that the Value's type is
|
||||
// SLICE.
|
||||
func (v Value) AsSlice() []Value {
|
||||
if v.vtype != SLICE {
|
||||
return nil
|
||||
}
|
||||
return v.asSlice()
|
||||
}
|
||||
|
||||
func (v Value) asSlice() []Value {
|
||||
switch vals := v.slice.(type) {
|
||||
case [0]Value:
|
||||
return []Value{}
|
||||
case [1]Value:
|
||||
return []Value{vals[0]}
|
||||
case [2]Value:
|
||||
return []Value{vals[0], vals[1]}
|
||||
case [3]Value:
|
||||
return []Value{vals[0], vals[1], vals[2]}
|
||||
case [4]Value:
|
||||
return []Value{vals[0], vals[1], vals[2], vals[3]}
|
||||
case [5]Value:
|
||||
return []Value{vals[0], vals[1], vals[2], vals[3], vals[4]}
|
||||
default:
|
||||
return asValueSliceReflect(v.slice)
|
||||
}
|
||||
}
|
||||
|
||||
func asValueSliceReflect(v any) []Value {
|
||||
rv := reflect.ValueOf(v)
|
||||
if !rv.IsValid() || rv.Kind() != reflect.Array || rv.Type().Elem() != reflect.TypeFor[Value]() {
|
||||
return nil
|
||||
}
|
||||
cpy := make([]Value, rv.Len())
|
||||
if len(cpy) > 0 {
|
||||
_ = reflect.Copy(reflect.ValueOf(cpy), rv)
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
// AsByteSlice returns the bytes value. Make sure that the Value's type
|
||||
// is BYTESLICE.
|
||||
func (v Value) AsByteSlice() []byte {
|
||||
if v.vtype != BYTESLICE {
|
||||
return nil
|
||||
}
|
||||
return v.asByteSlice()
|
||||
}
|
||||
|
||||
func (v Value) asByteSlice() []byte {
|
||||
return []byte(v.stringly)
|
||||
}
|
||||
|
||||
type unknownValueType struct{}
|
||||
|
||||
// AsInterface returns Value's data as any.
|
||||
@@ -236,13 +311,60 @@ func (v Value) AsInterface() any {
|
||||
return v.stringly
|
||||
case STRINGSLICE:
|
||||
return v.asStringSlice()
|
||||
case BYTESLICE:
|
||||
return v.asByteSlice()
|
||||
case SLICE:
|
||||
return v.asSlice()
|
||||
case EMPTY:
|
||||
return nil
|
||||
}
|
||||
return unknownValueType{}
|
||||
}
|
||||
|
||||
// String returns a string representation of Value using the
|
||||
// [OpenTelemetry AnyValue representation for non-OTLP protocols] rules.
|
||||
//
|
||||
// Strings are returned as-is without JSON quoting, booleans and integers use
|
||||
// JSON literals, floating-point values use JSON numbers except that NaN and
|
||||
// ±Inf are rendered as NaN, Infinity, and -Infinity, byte slices are
|
||||
// base64-encoded, empty values are the empty string, and slices are encoded as
|
||||
// JSON arrays. String, byte, and special floating-point values inside arrays
|
||||
// are encoded as JSON strings, and empty values inside arrays are encoded as
|
||||
// null.
|
||||
//
|
||||
// [OpenTelemetry AnyValue representation for non-OTLP protocols]: https://opentelemetry.io/docs/specs/otel/common/#anyvalue-representation-for-non-otlp-protocols
|
||||
func (v Value) String() string {
|
||||
switch v.Type() {
|
||||
case BOOL:
|
||||
return strconv.FormatBool(v.AsBool())
|
||||
case BOOLSLICE:
|
||||
return formatBoolSliceValue(v.slice)
|
||||
case INT64:
|
||||
return strconv.FormatInt(v.AsInt64(), 10)
|
||||
case INT64SLICE:
|
||||
return formatInt64SliceValue(v.slice)
|
||||
case FLOAT64:
|
||||
return formatFloat64(v.AsFloat64())
|
||||
case FLOAT64SLICE:
|
||||
return formatFloat64SliceValue(v.slice)
|
||||
case STRING:
|
||||
return v.stringly
|
||||
case STRINGSLICE:
|
||||
return formatStringSliceValue(v.slice)
|
||||
case BYTESLICE:
|
||||
return formatByteSlice(v.stringly)
|
||||
case SLICE:
|
||||
return formatValueSliceValue(v.slice)
|
||||
case EMPTY:
|
||||
return ""
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Emit returns a string representation of Value's data.
|
||||
//
|
||||
// Deprecated: Use [Value.String] instead.
|
||||
func (v Value) Emit() string {
|
||||
switch v.Type() {
|
||||
case BOOLSLICE:
|
||||
@@ -273,6 +395,10 @@ func (v Value) Emit() string {
|
||||
return string(j)
|
||||
case STRING:
|
||||
return v.stringly
|
||||
case BYTESLICE:
|
||||
return formatByteSlice(v.stringly)
|
||||
case SLICE:
|
||||
return formatValueSliceValue(v.slice)
|
||||
case EMPTY:
|
||||
return ""
|
||||
default:
|
||||
@@ -280,6 +406,622 @@ func (v Value) Emit() string {
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
jsonArrayBracketsLen = len("[]")
|
||||
boolArrayElemMaxLen = len("false")
|
||||
int64ArrayElemMaxLen = len("-9223372036854775808")
|
||||
float64ArrayElemMaxLen = len("-1.7976931348623157e+308")
|
||||
commaLen = len(",")
|
||||
)
|
||||
|
||||
func sliceValue(v []Value) any {
|
||||
switch len(v) {
|
||||
case 0:
|
||||
return [0]Value{}
|
||||
case 1:
|
||||
return [1]Value{v[0]}
|
||||
case 2:
|
||||
return [2]Value{v[0], v[1]}
|
||||
case 3:
|
||||
return [3]Value{v[0], v[1], v[2]}
|
||||
case 4:
|
||||
return [4]Value{v[0], v[1], v[2], v[3]}
|
||||
case 5:
|
||||
return [5]Value{v[0], v[1], v[2], v[3], v[4]}
|
||||
default:
|
||||
return sliceValueReflect(v)
|
||||
}
|
||||
}
|
||||
|
||||
func sliceValueReflect(v []Value) any {
|
||||
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[Value]())).Elem()
|
||||
reflect.Copy(cp, reflect.ValueOf(v))
|
||||
return cp.Interface()
|
||||
}
|
||||
|
||||
func formatBoolSliceValue(v any) string {
|
||||
switch vals := v.(type) {
|
||||
case [0]bool:
|
||||
return "[]"
|
||||
case [1]bool:
|
||||
return formatBoolSlice(vals[:])
|
||||
case [2]bool:
|
||||
return formatBoolSlice(vals[:])
|
||||
case [3]bool:
|
||||
return formatBoolSlice(vals[:])
|
||||
default:
|
||||
return formatBoolSliceReflect(v)
|
||||
}
|
||||
}
|
||||
|
||||
func formatBoolSlice(vals []bool) string {
|
||||
var b strings.Builder
|
||||
appendBoolSlice(&b, vals)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatBoolSliceReflect(v any) string {
|
||||
var b strings.Builder
|
||||
appendBoolSliceReflect(&b, reflect.ValueOf(v))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func appendBoolSliceValue(dst *strings.Builder, v any) {
|
||||
switch vals := v.(type) {
|
||||
case [0]bool:
|
||||
_, _ = dst.WriteString("[]")
|
||||
case [1]bool:
|
||||
appendBoolSlice(dst, vals[:])
|
||||
case [2]bool:
|
||||
appendBoolSlice(dst, vals[:])
|
||||
case [3]bool:
|
||||
appendBoolSlice(dst, vals[:])
|
||||
default:
|
||||
appendBoolSliceReflect(dst, reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
func appendBoolSlice(dst *strings.Builder, vals []bool) {
|
||||
dst.Grow(jsonArrayBracketsLen + len(vals)*(boolArrayElemMaxLen+commaLen))
|
||||
_ = dst.WriteByte('[')
|
||||
for i, val := range vals {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
if val {
|
||||
_, _ = dst.WriteString("true")
|
||||
} else {
|
||||
_, _ = dst.WriteString("false")
|
||||
}
|
||||
}
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func appendBoolSliceReflect(dst *strings.Builder, rv reflect.Value) {
|
||||
dst.Grow(jsonArrayBracketsLen + rv.Len()*(boolArrayElemMaxLen+commaLen))
|
||||
_ = dst.WriteByte('[')
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
if rv.Index(i).Bool() {
|
||||
_, _ = dst.WriteString("true")
|
||||
} else {
|
||||
_, _ = dst.WriteString("false")
|
||||
}
|
||||
}
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func formatInt64SliceValue(v any) string {
|
||||
switch vals := v.(type) {
|
||||
case [0]int64:
|
||||
return "[]"
|
||||
case [1]int64:
|
||||
return formatInt64Slice(vals[:])
|
||||
case [2]int64:
|
||||
return formatInt64Slice(vals[:])
|
||||
case [3]int64:
|
||||
return formatInt64Slice(vals[:])
|
||||
default:
|
||||
return formatInt64SliceReflect(v)
|
||||
}
|
||||
}
|
||||
|
||||
func formatInt64Slice(vals []int64) string {
|
||||
var b strings.Builder
|
||||
appendInt64Slice(&b, vals)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatInt64SliceReflect(v any) string {
|
||||
var b strings.Builder
|
||||
appendInt64SliceReflect(&b, reflect.ValueOf(v))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func appendInt64SliceValue(dst *strings.Builder, v any) {
|
||||
switch vals := v.(type) {
|
||||
case [0]int64:
|
||||
_, _ = dst.WriteString("[]")
|
||||
case [1]int64:
|
||||
appendInt64Slice(dst, vals[:])
|
||||
case [2]int64:
|
||||
appendInt64Slice(dst, vals[:])
|
||||
case [3]int64:
|
||||
appendInt64Slice(dst, vals[:])
|
||||
default:
|
||||
appendInt64SliceReflect(dst, reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
func appendInt64Slice(dst *strings.Builder, vals []int64) {
|
||||
dst.Grow(jsonArrayBracketsLen + len(vals)*(int64ArrayElemMaxLen+commaLen))
|
||||
_ = dst.WriteByte('[')
|
||||
|
||||
var buf [int64ArrayElemMaxLen]byte
|
||||
for i, val := range vals {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
out := strconv.AppendInt(buf[:0], val, 10)
|
||||
_, _ = dst.Write(out)
|
||||
}
|
||||
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func appendInt64SliceReflect(dst *strings.Builder, rv reflect.Value) {
|
||||
dst.Grow(jsonArrayBracketsLen + rv.Len()*(int64ArrayElemMaxLen+commaLen))
|
||||
_ = dst.WriteByte('[')
|
||||
|
||||
var scratch [int64ArrayElemMaxLen]byte
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
out := strconv.AppendInt(scratch[:0], rv.Index(i).Int(), 10)
|
||||
_, _ = dst.Write(out)
|
||||
}
|
||||
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func formatFloat64(v float64) string {
|
||||
switch {
|
||||
case math.IsNaN(v):
|
||||
return "NaN"
|
||||
case math.IsInf(v, 1):
|
||||
return "Infinity"
|
||||
case math.IsInf(v, -1):
|
||||
return "-Infinity"
|
||||
default:
|
||||
return strconv.FormatFloat(v, 'g', -1, 64)
|
||||
}
|
||||
}
|
||||
|
||||
func formatFloat64SliceValue(v any) string {
|
||||
switch vals := v.(type) {
|
||||
case [0]float64:
|
||||
return "[]"
|
||||
case [1]float64:
|
||||
return formatFloat64Slice(vals[:])
|
||||
case [2]float64:
|
||||
return formatFloat64Slice(vals[:])
|
||||
case [3]float64:
|
||||
return formatFloat64Slice(vals[:])
|
||||
default:
|
||||
return formatFloat64SliceReflect(v)
|
||||
}
|
||||
}
|
||||
|
||||
func formatFloat64Slice(vals []float64) string {
|
||||
var b strings.Builder
|
||||
appendFloat64Slice(&b, vals)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatFloat64SliceReflect(v any) string {
|
||||
var b strings.Builder
|
||||
appendFloat64SliceReflect(&b, reflect.ValueOf(v))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func appendFloat64SliceValue(dst *strings.Builder, v any) {
|
||||
switch vals := v.(type) {
|
||||
case [0]float64:
|
||||
_, _ = dst.WriteString("[]")
|
||||
case [1]float64:
|
||||
appendFloat64Slice(dst, vals[:])
|
||||
case [2]float64:
|
||||
appendFloat64Slice(dst, vals[:])
|
||||
case [3]float64:
|
||||
appendFloat64Slice(dst, vals[:])
|
||||
default:
|
||||
appendFloat64SliceReflect(dst, reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
func appendFloat64Slice(dst *strings.Builder, vals []float64) {
|
||||
dst.Grow(jsonArrayBracketsLen + len(vals)*(float64ArrayElemMaxLen+commaLen))
|
||||
_ = dst.WriteByte('[')
|
||||
|
||||
var buf [float64ArrayElemMaxLen]byte
|
||||
for i, val := range vals {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
|
||||
switch {
|
||||
case math.IsNaN(val):
|
||||
_, _ = dst.WriteString(`"NaN"`)
|
||||
case math.IsInf(val, 1):
|
||||
_, _ = dst.WriteString(`"Infinity"`)
|
||||
case math.IsInf(val, -1):
|
||||
_, _ = dst.WriteString(`"-Infinity"`)
|
||||
default:
|
||||
out := strconv.AppendFloat(buf[:0], val, 'g', -1, 64)
|
||||
_, _ = dst.Write(out)
|
||||
}
|
||||
}
|
||||
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func appendFloat64SliceReflect(dst *strings.Builder, rv reflect.Value) {
|
||||
dst.Grow(jsonArrayBracketsLen + rv.Len()*(float64ArrayElemMaxLen+commaLen))
|
||||
_ = dst.WriteByte('[')
|
||||
|
||||
var scratch [float64ArrayElemMaxLen]byte
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
val := rv.Index(i).Float()
|
||||
switch {
|
||||
case math.IsNaN(val):
|
||||
_, _ = dst.WriteString(`"NaN"`)
|
||||
case math.IsInf(val, 1):
|
||||
_, _ = dst.WriteString(`"Infinity"`)
|
||||
case math.IsInf(val, -1):
|
||||
_, _ = dst.WriteString(`"-Infinity"`)
|
||||
default:
|
||||
out := strconv.AppendFloat(scratch[:0], val, 'g', -1, 64)
|
||||
_, _ = dst.Write(out)
|
||||
}
|
||||
}
|
||||
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func formatStringSliceValue(v any) string {
|
||||
switch vals := v.(type) {
|
||||
case [0]string:
|
||||
return "[]"
|
||||
case [1]string:
|
||||
return formatStringSlice(vals[:])
|
||||
case [2]string:
|
||||
return formatStringSlice(vals[:])
|
||||
case [3]string:
|
||||
return formatStringSlice(vals[:])
|
||||
default:
|
||||
return formatStringSliceReflect(v)
|
||||
}
|
||||
}
|
||||
|
||||
func formatStringSlice(vals []string) string {
|
||||
var b strings.Builder
|
||||
appendStringSlice(&b, vals)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatStringSliceReflect(v any) string {
|
||||
var b strings.Builder
|
||||
appendStringSliceReflect(&b, reflect.ValueOf(v))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func appendStringSliceValue(dst *strings.Builder, v any) {
|
||||
switch vals := v.(type) {
|
||||
case [0]string:
|
||||
_, _ = dst.WriteString("[]")
|
||||
case [1]string:
|
||||
appendStringSlice(dst, vals[:])
|
||||
case [2]string:
|
||||
appendStringSlice(dst, vals[:])
|
||||
case [3]string:
|
||||
appendStringSlice(dst, vals[:])
|
||||
default:
|
||||
appendStringSliceReflect(dst, reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
func appendStringSlice(dst *strings.Builder, vals []string) {
|
||||
size := jsonArrayBracketsLen
|
||||
for _, val := range vals {
|
||||
size += len(val) + commaLen + 2 // Account for JSON string quotes and comma.
|
||||
}
|
||||
|
||||
dst.Grow(size)
|
||||
_ = dst.WriteByte('[')
|
||||
for i, val := range vals {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
appendJSONString(dst, val)
|
||||
}
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func appendStringSliceReflect(dst *strings.Builder, rv reflect.Value) {
|
||||
size := jsonArrayBracketsLen
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
size += len(rv.Index(i).String()) + commaLen + 2 // Account for JSON string quotes and comma.
|
||||
}
|
||||
|
||||
dst.Grow(size)
|
||||
_ = dst.WriteByte('[')
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
appendJSONString(dst, rv.Index(i).String())
|
||||
}
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func formatByteSlice(v string) string {
|
||||
var b strings.Builder
|
||||
appendBase64(&b, v)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatValueSliceValue(v any) string {
|
||||
switch vals := v.(type) {
|
||||
case [0]Value:
|
||||
return "[]"
|
||||
case [1]Value:
|
||||
return formatValueSlice(vals[:])
|
||||
case [2]Value:
|
||||
return formatValueSlice(vals[:])
|
||||
case [3]Value:
|
||||
return formatValueSlice(vals[:])
|
||||
case [4]Value:
|
||||
return formatValueSlice(vals[:])
|
||||
case [5]Value:
|
||||
return formatValueSlice(vals[:])
|
||||
default:
|
||||
return formatValueSliceReflect(v)
|
||||
}
|
||||
}
|
||||
|
||||
func formatValueSlice(vals []Value) string {
|
||||
var b strings.Builder
|
||||
appendValueSlice(&b, vals)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatValueSliceReflect(v any) string {
|
||||
var b strings.Builder
|
||||
appendValueSliceReflect(&b, reflect.ValueOf(v))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func appendValueSliceValue(dst *strings.Builder, v any) {
|
||||
switch vals := v.(type) {
|
||||
case [0]Value:
|
||||
_, _ = dst.WriteString("[]")
|
||||
case [1]Value:
|
||||
appendValueSlice(dst, vals[:])
|
||||
case [2]Value:
|
||||
appendValueSlice(dst, vals[:])
|
||||
case [3]Value:
|
||||
appendValueSlice(dst, vals[:])
|
||||
case [4]Value:
|
||||
appendValueSlice(dst, vals[:])
|
||||
case [5]Value:
|
||||
appendValueSlice(dst, vals[:])
|
||||
default:
|
||||
appendValueSliceReflect(dst, reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
func appendValueSlice(dst *strings.Builder, vals []Value) {
|
||||
// Estimate 10 bytes per value for small values and commas.
|
||||
dst.Grow(jsonArrayBracketsLen + len(vals)*commaLen + len(vals)*10)
|
||||
_ = dst.WriteByte('[')
|
||||
for i, val := range vals {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
appendJSONValue(dst, val)
|
||||
}
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func appendValueSliceReflect(dst *strings.Builder, rv reflect.Value) {
|
||||
// Estimate 10 bytes per value for small values and commas.
|
||||
dst.Grow(jsonArrayBracketsLen + rv.Len()*commaLen + rv.Len()*10)
|
||||
_ = dst.WriteByte('[')
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
appendJSONValue(dst, rv.Index(i).Interface().(Value))
|
||||
}
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func appendJSONValue(dst *strings.Builder, v Value) {
|
||||
switch v.Type() {
|
||||
case BOOL:
|
||||
if v.AsBool() {
|
||||
_, _ = dst.WriteString("true")
|
||||
} else {
|
||||
_, _ = dst.WriteString("false")
|
||||
}
|
||||
case BOOLSLICE:
|
||||
appendBoolSliceValue(dst, v.slice)
|
||||
case INT64:
|
||||
var buf [int64ArrayElemMaxLen]byte
|
||||
out := strconv.AppendInt(buf[:0], v.AsInt64(), 10)
|
||||
_, _ = dst.Write(out)
|
||||
case INT64SLICE:
|
||||
appendInt64SliceValue(dst, v.slice)
|
||||
case FLOAT64:
|
||||
val := v.AsFloat64()
|
||||
switch {
|
||||
case math.IsNaN(val):
|
||||
appendJSONString(dst, "NaN")
|
||||
case math.IsInf(val, 1):
|
||||
appendJSONString(dst, "Infinity")
|
||||
case math.IsInf(val, -1):
|
||||
appendJSONString(dst, "-Infinity")
|
||||
default:
|
||||
var buf [float64ArrayElemMaxLen]byte
|
||||
out := strconv.AppendFloat(buf[:0], val, 'g', -1, 64)
|
||||
_, _ = dst.Write(out)
|
||||
}
|
||||
case FLOAT64SLICE:
|
||||
appendFloat64SliceValue(dst, v.slice)
|
||||
case STRING:
|
||||
appendJSONString(dst, v.stringly)
|
||||
case STRINGSLICE:
|
||||
appendStringSliceValue(dst, v.slice)
|
||||
case BYTESLICE:
|
||||
_ = dst.WriteByte('"')
|
||||
appendBase64(dst, v.stringly)
|
||||
_ = dst.WriteByte('"')
|
||||
case SLICE:
|
||||
appendValueSliceValue(dst, v.slice)
|
||||
case EMPTY:
|
||||
_, _ = dst.WriteString("null")
|
||||
default:
|
||||
appendJSONString(dst, "unknown")
|
||||
}
|
||||
}
|
||||
|
||||
// appendJSONString appends s to dst as a JSON string literal.
|
||||
//
|
||||
// This is adapted from the Go standard library's encoding/json
|
||||
// [appendString implementation]. It keeps the same escaping behavior we need
|
||||
// here, but writes directly into a strings.Builder and intentionally does not
|
||||
// apply HTML escaping because the OpenTelemetry non-OTLP AnyValue representation
|
||||
// only requires JSON array string encoding. We inline this instead of using
|
||||
// encoding/json so slice formatting avoids allocations and reflection.
|
||||
//
|
||||
// [appendString implementation]: https://github.com/golang/go/blob/3b5954c6349d31465dca409b45ab6597e0942d9f/src/encoding/json/encode.go#L998-L1064
|
||||
func appendJSONString(dst *strings.Builder, s string) {
|
||||
const hex = "0123456789abcdef" // For escaping bytes to hex.
|
||||
|
||||
_ = dst.WriteByte('"')
|
||||
start := 0
|
||||
|
||||
for i := 0; i < len(s); {
|
||||
if c := s[i]; c < utf8.RuneSelf {
|
||||
if c >= 0x20 && c != '\\' && c != '"' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
if start < i {
|
||||
_, _ = dst.WriteString(s[start:i])
|
||||
}
|
||||
|
||||
switch c {
|
||||
case '\\', '"':
|
||||
_ = dst.WriteByte('\\')
|
||||
_ = dst.WriteByte(c)
|
||||
case '\b':
|
||||
_, _ = dst.WriteString(`\b`)
|
||||
case '\f':
|
||||
_, _ = dst.WriteString(`\f`)
|
||||
case '\n':
|
||||
_, _ = dst.WriteString(`\n`)
|
||||
case '\r':
|
||||
_, _ = dst.WriteString(`\r`)
|
||||
case '\t':
|
||||
_, _ = dst.WriteString(`\t`)
|
||||
default:
|
||||
_, _ = dst.WriteString(`\u00`)
|
||||
_ = dst.WriteByte(hex[c>>4])
|
||||
_ = dst.WriteByte(hex[c&0x0f])
|
||||
}
|
||||
|
||||
i++
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
|
||||
r, size := utf8.DecodeRuneInString(s[i:])
|
||||
if r == utf8.RuneError && size == 1 {
|
||||
if start < i {
|
||||
_, _ = dst.WriteString(s[start:i])
|
||||
}
|
||||
// Match encoding/json by replacing invalid UTF-8 with U+FFFD.
|
||||
_, _ = dst.WriteString(`\ufffd`)
|
||||
i++
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
|
||||
if r == '\u2028' || r == '\u2029' {
|
||||
if start < i {
|
||||
_, _ = dst.WriteString(s[start:i])
|
||||
}
|
||||
// Escape JSONP-sensitive separators unconditionally, like encoding/json.
|
||||
_, _ = dst.WriteString(`\u202`)
|
||||
_ = dst.WriteByte(hex[r&0x0f])
|
||||
i += size
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
|
||||
i += size
|
||||
}
|
||||
|
||||
if start < len(s) {
|
||||
_, _ = dst.WriteString(s[start:])
|
||||
}
|
||||
_ = dst.WriteByte('"')
|
||||
}
|
||||
|
||||
// This is adapted from the Go standard library's encoding/base64
|
||||
// [Encoding.Encode implementation]. It keeps the same encoding behavior we need
|
||||
// here, but writes directly into a strings.Builder. We inline this instead of using
|
||||
// encoding/base64 to avoid allocations.
|
||||
//
|
||||
// [Encoding.Encode implementation]: https://github.com/golang/go/blob/3b5954c6349d31465dca409b45ab6597e0942d9f/src/encoding/base64/base64.go#L139-L189
|
||||
func appendBase64(dst *strings.Builder, s string) {
|
||||
const encode = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
|
||||
dst.Grow(base64.StdEncoding.EncodedLen(len(s)))
|
||||
|
||||
i := 0
|
||||
for ; i+2 < len(s); i += 3 {
|
||||
n := uint32(s[i])<<16 | uint32(s[i+1])<<8 | uint32(s[i+2])
|
||||
_ = dst.WriteByte(encode[n>>18&0x3f])
|
||||
_ = dst.WriteByte(encode[n>>12&0x3f])
|
||||
_ = dst.WriteByte(encode[n>>6&0x3f])
|
||||
_ = dst.WriteByte(encode[n&0x3f])
|
||||
}
|
||||
|
||||
switch len(s) - i {
|
||||
case 1:
|
||||
n := uint32(s[i]) << 16
|
||||
_ = dst.WriteByte(encode[n>>18&0x3f])
|
||||
_ = dst.WriteByte(encode[n>>12&0x3f])
|
||||
_ = dst.WriteByte('=')
|
||||
_ = dst.WriteByte('=')
|
||||
case 2:
|
||||
n := uint32(s[i])<<16 | uint32(s[i+1])<<8
|
||||
_ = dst.WriteByte(encode[n>>18&0x3f])
|
||||
_ = dst.WriteByte(encode[n>>12&0x3f])
|
||||
_ = dst.WriteByte(encode[n>>6&0x3f])
|
||||
_ = dst.WriteByte('=')
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON returns the JSON encoding of the Value.
|
||||
func (v Value) MarshalJSON() ([]byte, error) {
|
||||
var jsonVal struct {
|
||||
|
||||
+26
-4
@@ -14,6 +14,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
maxParseErrors = 5
|
||||
|
||||
// W3C Baggage specification limits.
|
||||
// https://www.w3.org/TR/baggage/#limits
|
||||
maxMembers = 64
|
||||
maxBytesPerBaggageString = 8192
|
||||
|
||||
@@ -493,9 +497,15 @@ func New(members ...Member) (Baggage, error) {
|
||||
// from the W3C Baggage specification which allows duplicate list-members, but
|
||||
// conforms to the OpenTelemetry Baggage specification.
|
||||
//
|
||||
// If the baggage-string exceeds the maximum allowed members (64) or bytes
|
||||
// (8192), members are dropped until the limits are satisfied and an error is
|
||||
// returned along with the partial result.
|
||||
// If the raw baggage-string exceeds the maximum allowed bytes (8192), an
|
||||
// empty Baggage and an error are returned.
|
||||
//
|
||||
// Otherwise, members are parsed left-to-right and accumulated until one of
|
||||
// the following conditions is reached, at which point parsing stops and an
|
||||
// error is returned alongside the partial result:
|
||||
// - accepting the next member would cause the encoded baggage to exceed
|
||||
// 8192 bytes, or
|
||||
// - the baggage already contains 64 distinct keys.
|
||||
//
|
||||
// Invalid members are skipped and the error is returned along with the
|
||||
// partial result containing the valid members.
|
||||
@@ -504,9 +514,14 @@ func Parse(bStr string) (Baggage, error) {
|
||||
return Baggage{}, nil
|
||||
}
|
||||
|
||||
if n := len(bStr); n > maxBytesPerBaggageString {
|
||||
return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
|
||||
}
|
||||
|
||||
b := make(baggage.List)
|
||||
sizes := make(map[string]int) // Track per-key byte sizes
|
||||
var totalBytes int
|
||||
var parseErrors int
|
||||
var truncateErr error
|
||||
for memberStr := range strings.SplitSeq(bStr, listDelimiter) {
|
||||
// Check member count limit.
|
||||
@@ -517,7 +532,10 @@ func Parse(bStr string) (Baggage, error) {
|
||||
|
||||
m, err := parseMember(memberStr)
|
||||
if err != nil {
|
||||
truncateErr = errors.Join(truncateErr, err)
|
||||
parseErrors++
|
||||
if parseErrors <= maxParseErrors {
|
||||
truncateErr = errors.Join(truncateErr, err)
|
||||
}
|
||||
continue // skip invalid member, keep processing
|
||||
}
|
||||
|
||||
@@ -553,6 +571,10 @@ func Parse(bStr string) (Baggage, error) {
|
||||
totalBytes = newTotalBytes
|
||||
}
|
||||
|
||||
if dropped := parseErrors - maxParseErrors; dropped > 0 {
|
||||
truncateErr = errors.Join(truncateErr, fmt.Errorf("and %d more invalid member(s)", dropped))
|
||||
}
|
||||
|
||||
if len(b) == 0 {
|
||||
return Baggage{}, truncateErr
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# This is a renovate-friendly source of Docker images.
|
||||
FROM python:3.13.6-slim-bullseye@sha256:e98b521460ee75bca92175c16247bdf7275637a8faaeb2bcfa19d879ae5c4b9a AS python
|
||||
FROM otel/weaver:v0.22.1@sha256:33ae522ae4b71c1c562563c1d81f46aa0f79f088a0873199143a1f11ac30e5c9 AS weaver
|
||||
FROM otel/weaver:v0.23.0@sha256:7984ecb55b859eb3034ae9d836c4eeda137e2bdd0873b7ba2bb6c3d24d6ff457 AS weaver
|
||||
FROM avtodev/markdown-lint:v1@sha256:6aeedc2f49138ce7a1cd0adffc1b1c0321b841dc2102408967d9301c031949ee AS markdown
|
||||
|
||||
Generated
Vendored
+18
-9
@@ -6,6 +6,7 @@ package otlpmetricgrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlpme
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
colmetricpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf"
|
||||
@@ -22,9 +24,10 @@ import (
|
||||
)
|
||||
|
||||
type client struct {
|
||||
metadata metadata.MD
|
||||
exportTimeout time.Duration
|
||||
requestFunc retry.RequestFunc
|
||||
metadata metadata.MD
|
||||
exportTimeout time.Duration
|
||||
maxRequestSize int
|
||||
requestFunc retry.RequestFunc
|
||||
|
||||
// ourConn keeps track of where conn was created: true if created here in
|
||||
// NewClient, or false if passed with an option. This is important on
|
||||
@@ -38,9 +41,10 @@ type client struct {
|
||||
// newClient creates a new gRPC metric client.
|
||||
func newClient(_ context.Context, cfg oconf.Config) (*client, error) {
|
||||
c := &client{
|
||||
exportTimeout: cfg.Metrics.Timeout,
|
||||
requestFunc: cfg.RetryConfig.RequestFunc(retryable),
|
||||
conn: cfg.GRPCConn,
|
||||
exportTimeout: cfg.Metrics.Timeout,
|
||||
maxRequestSize: cfg.Metrics.MaxRequestSize,
|
||||
requestFunc: cfg.RetryConfig.RequestFunc(retryable),
|
||||
conn: cfg.GRPCConn,
|
||||
}
|
||||
|
||||
if len(cfg.Metrics.Headers) > 0 {
|
||||
@@ -115,10 +119,15 @@ func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou
|
||||
ctx, cancel := c.exportContext(ctx)
|
||||
defer cancel()
|
||||
|
||||
pbRequest := &colmetricpb.ExportMetricsServiceRequest{
|
||||
ResourceMetrics: []*metricpb.ResourceMetrics{protoMetrics},
|
||||
}
|
||||
if maxSize := c.maxRequestSize; maxSize > 0 && proto.Size(pbRequest) > maxSize {
|
||||
return fmt.Errorf("request message too large: exceeded %d bytes", maxSize)
|
||||
}
|
||||
|
||||
return errors.Join(uploadErr, c.requestFunc(ctx, func(iCtx context.Context) error {
|
||||
resp, err := c.msc.Export(iCtx, &colmetricpb.ExportMetricsServiceRequest{
|
||||
ResourceMetrics: []*metricpb.ResourceMetrics{protoMetrics},
|
||||
})
|
||||
resp, err := c.msc.Export(iCtx, pbRequest)
|
||||
if resp != nil && resp.PartialSuccess != nil {
|
||||
msg := resp.PartialSuccess.GetErrorMessage()
|
||||
n := resp.PartialSuccess.GetRejectedDataPoints()
|
||||
|
||||
Generated
Vendored
+10
@@ -231,6 +231,16 @@ func WithTimeout(duration time.Duration) Option {
|
||||
return wrappedOption{oconf.WithTimeout(duration)}
|
||||
}
|
||||
|
||||
// WithMaxRequestSize sets the maximum size, in bytes, of a serialized export
|
||||
// request, before compression, that the exporter will send.
|
||||
//
|
||||
// If size is less than or equal to zero, no request-size limit is applied.
|
||||
// Disabling the limit is not recommended because it can lead to excessive
|
||||
// resource consumption or abuse.
|
||||
func WithMaxRequestSize(size int) Option {
|
||||
return wrappedOption{oconf.WithMaxRequestSize(size)}
|
||||
}
|
||||
|
||||
// WithRetry sets the retry policy for transient retryable errors that are
|
||||
// returned by the target endpoint.
|
||||
//
|
||||
|
||||
+3
@@ -77,6 +77,9 @@ default aggregation to use for histogram instruments. Supported values:
|
||||
|
||||
The configuration can be overridden by [WithAggregationSelector] option.
|
||||
|
||||
See [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/x] for information about
|
||||
the experimental features.
|
||||
|
||||
[W3C Baggage HTTP Header Content Format]: https://www.w3.org/TR/baggage/#header-content
|
||||
[Explicit Bucket Histogram Aggregation]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.26.0/specification/metrics/sdk.md#explicit-bucket-histogram-aggregation
|
||||
[Base2 Exponential Bucket Histogram Aggregation]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.26.0/specification/metrics/sdk.md#base2-exponential-bucket-histogram-aggregation
|
||||
|
||||
Generated
Vendored
+31
-2
@@ -11,8 +11,11 @@ import (
|
||||
|
||||
metricpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/counter"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/observ"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/transform"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/x"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
@@ -31,6 +34,9 @@ type Exporter struct {
|
||||
aggregationSelector metric.AggregationSelector
|
||||
|
||||
shutdownOnce sync.Once
|
||||
|
||||
// Self-observability metrics
|
||||
inst *observ.Instrumentation
|
||||
}
|
||||
|
||||
func newExporter(c *client, cfg oconf.Config) (*Exporter, error) {
|
||||
@@ -46,12 +52,27 @@ func newExporter(c *client, cfg oconf.Config) (*Exporter, error) {
|
||||
as = metric.DefaultAggregationSelector
|
||||
}
|
||||
|
||||
var inst *observ.Instrumentation
|
||||
var initErr error
|
||||
if x.Observability.Enabled() {
|
||||
var err error
|
||||
inst, err = observ.NewInstrumentation(
|
||||
counter.NextExporterID(),
|
||||
c.conn.CanonicalTarget(),
|
||||
)
|
||||
if err != nil {
|
||||
initErr = err
|
||||
}
|
||||
}
|
||||
|
||||
return &Exporter{
|
||||
client: c,
|
||||
|
||||
temporalitySelector: ts,
|
||||
aggregationSelector: as,
|
||||
}, nil
|
||||
|
||||
inst: inst,
|
||||
}, initErr
|
||||
}
|
||||
|
||||
// Temporality returns the Temporality to use for an instrument kind.
|
||||
@@ -72,10 +93,18 @@ func (e *Exporter) Export(ctx context.Context, rm *metricdata.ResourceMetrics) e
|
||||
defer global.Debug("OTLP/gRPC exporter export", "Data", rm)
|
||||
|
||||
otlpRm, err := transform.ResourceMetrics(rm)
|
||||
|
||||
// Track export operation for self-observability
|
||||
op := e.inst.TrackExport(ctx, otlpRm)
|
||||
|
||||
var upErr error
|
||||
defer func() { op.End(upErr) }()
|
||||
|
||||
// Best effort upload of transformable metrics.
|
||||
e.clientMu.Lock()
|
||||
upErr := e.client.UploadMetrics(ctx, otlpRm)
|
||||
upErr = e.client.UploadMetrics(ctx, otlpRm)
|
||||
e.clientMu.Unlock()
|
||||
|
||||
if upErr != nil {
|
||||
if err == nil {
|
||||
return fmt.Errorf("failed to upload metrics: %w", upErr)
|
||||
|
||||
vendor/go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/counter/counter.go
Generated
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/counter/counter.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package counter provides a simple counter for generating unique IDs.
|
||||
//
|
||||
// This package is used to generate unique IDs while allowing testing packages
|
||||
// to reset the counter.
|
||||
package counter // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/counter"
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
// exporterN is a global 0-based count of the number of exporters created.
|
||||
var exporterN atomic.Int64
|
||||
|
||||
// NextExporterID returns the next unique ID for an exporter.
|
||||
func NextExporterID() int64 {
|
||||
const inc = 1
|
||||
return exporterN.Add(inc) - inc
|
||||
}
|
||||
|
||||
// SetExporterID sets the exporter ID counter to v and returns the previous
|
||||
// value.
|
||||
//
|
||||
// This function is useful for testing purposes, allowing you to reset the
|
||||
// counter. It should not be used in production code.
|
||||
func SetExporterID(v int64) int64 {
|
||||
return exporterN.Swap(v)
|
||||
}
|
||||
Generated
Vendored
+9
@@ -7,6 +7,9 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/o
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/partialsuccess.go.tmpl "--data={}" --out=partialsuccess.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/partialsuccess_test.go.tmpl "--data={}" --out=partialsuccess_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/observ/target.go.tmpl "--data={ \"pkg\": \"observ\", \"pkg_path\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/observ\" }" --out=observ/target.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/observ/target_test.go.tmpl "--data={ \"pkg\": \"observ\" }" --out=observ/target_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/retry/retry.go.tmpl "--data={}" --out=retry/retry.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/retry/retry_test.go.tmpl "--data={}" --out=retry/retry_test.go
|
||||
|
||||
@@ -30,3 +33,9 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/o
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/error_test.go.tmpl "--data={}" --out=transform/error_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata.go.tmpl "--data={}" --out=transform/metricdata.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata_test.go.tmpl "--data={}" --out=transform/metricdata_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/counter/counter.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/counter\" }" --out=counter/counter.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/counter/counter_test.go.tmpl "--data={}" --out=counter/counter_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/x/x.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc\" }" --out=x/x.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/x/x_test.go.tmpl "--data={}" --out=x/x_test.go
|
||||
|
||||
Generated
Vendored
+343
@@ -0,0 +1,343 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package observ provides self-observability metrics for OTLP metric exporters.
|
||||
// This is an experimental feature controlled by the x.Observability feature flag.
|
||||
package observ // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/observ"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/sdk"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0/otelconv"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
metricpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/x"
|
||||
)
|
||||
|
||||
var (
|
||||
attrPool = sync.Pool{
|
||||
New: func() any {
|
||||
// Pre-allocate for common attributes and dynamic error attributes.
|
||||
const n = 1 /* otel.component.type */ +
|
||||
1 /* otel.component.name */ +
|
||||
1 /* server.address */ +
|
||||
1 /* server.port */ +
|
||||
1 /* error.type */ +
|
||||
1 /* rpc.grpc.status_code */
|
||||
s := make([]attribute.KeyValue, 0, n)
|
||||
return &s
|
||||
},
|
||||
}
|
||||
|
||||
recOptPool = sync.Pool{
|
||||
New: func() any {
|
||||
o := make([]metric.RecordOption, 0, 1)
|
||||
return &o
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Instrumentation holds the self-observability metric instruments for an OTLP metric exporter.
|
||||
type Instrumentation struct {
|
||||
exported otelconv.SDKExporterMetricDataPointExported
|
||||
inflight otelconv.SDKExporterMetricDataPointInflight
|
||||
duration otelconv.SDKExporterOperationDuration
|
||||
attrs []attribute.KeyValue
|
||||
addOpt metric.AddOption
|
||||
recOpt metric.RecordOption
|
||||
}
|
||||
|
||||
// NewInstrumentation returns instrumentation for an OTLP over gRPC metric
|
||||
// exporter with the provided ID using the global MeterProvider.
|
||||
//
|
||||
// The id should be the unique exporter instance ID. It is used
|
||||
// to set the "component.name" attribute.
|
||||
//
|
||||
// The target is the endpoint the exporter is exporting to.
|
||||
//
|
||||
// If the experimental observability is disabled, nil is returned.
|
||||
func NewInstrumentation(id int64, target string) (*Instrumentation, error) {
|
||||
if !x.Observability.Enabled() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
em := &Instrumentation{}
|
||||
|
||||
meter := otel.GetMeterProvider().Meter(
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc",
|
||||
metric.WithInstrumentationVersion(sdk.Version()),
|
||||
metric.WithSchemaURL(semconv.SchemaURL),
|
||||
)
|
||||
|
||||
var err error
|
||||
var instrumentErr error
|
||||
|
||||
em.exported, instrumentErr = otelconv.NewSDKExporterMetricDataPointExported(meter)
|
||||
if instrumentErr != nil {
|
||||
err = errors.Join(err, fmt.Errorf("failed to create exported metric: %w", instrumentErr))
|
||||
}
|
||||
|
||||
em.inflight, instrumentErr = otelconv.NewSDKExporterMetricDataPointInflight(meter)
|
||||
if instrumentErr != nil {
|
||||
err = errors.Join(err, fmt.Errorf("failed to create inflight metric: %w", instrumentErr))
|
||||
}
|
||||
|
||||
em.duration, instrumentErr = otelconv.NewSDKExporterOperationDuration(meter)
|
||||
if instrumentErr != nil {
|
||||
err = errors.Join(err, fmt.Errorf("failed to create duration metric: %w", instrumentErr))
|
||||
}
|
||||
|
||||
em.attrs = BaseAttrs(id, target)
|
||||
|
||||
attrSet := attribute.NewSet(em.attrs...)
|
||||
em.addOpt = metric.WithAttributeSet(attrSet)
|
||||
em.recOpt = metric.WithAttributeSet(attribute.NewSet(append(
|
||||
[]attribute.KeyValue{semconv.RPCResponseStatusCode(codes.OK.String())},
|
||||
em.attrs...,
|
||||
)...))
|
||||
|
||||
return em, err
|
||||
}
|
||||
|
||||
// ComponentName returns the component name for the exporter with the
|
||||
// provided ID.
|
||||
func ComponentName(id int64) string {
|
||||
t := string(otelconv.ComponentTypeOtlpGRPCMetricExporter)
|
||||
return fmt.Sprintf("%s/%d", t, id)
|
||||
}
|
||||
|
||||
// BaseAttrs returns the base attributes for the exporter with the provided ID
|
||||
// and target.
|
||||
//
|
||||
// The id should be the unique exporter instance ID. It is used
|
||||
// to set the "component.name" attribute.
|
||||
//
|
||||
// The target is the gRPC target the exporter is exporting to. It is expected
|
||||
// to be the output of the Client's CanonicalTarget method.
|
||||
func BaseAttrs(id int64, target string) []attribute.KeyValue {
|
||||
host, port, err := ParseCanonicalTarget(target)
|
||||
if err != nil || (host == "" && port < 0) {
|
||||
if err != nil {
|
||||
global.Debug("failed to parse target", "target", target, "error", err)
|
||||
}
|
||||
return []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeKey.String(string(otelconv.ComponentTypeOtlpGRPCMetricExporter)),
|
||||
}
|
||||
}
|
||||
|
||||
// Do not use append so the slice is exactly allocated.
|
||||
|
||||
if port < 0 {
|
||||
return []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeKey.String(string(otelconv.ComponentTypeOtlpGRPCMetricExporter)),
|
||||
semconv.ServerAddress(host),
|
||||
}
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
return []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeKey.String(string(otelconv.ComponentTypeOtlpGRPCMetricExporter)),
|
||||
semconv.ServerPort(port),
|
||||
}
|
||||
}
|
||||
|
||||
return []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeKey.String(string(otelconv.ComponentTypeOtlpGRPCMetricExporter)),
|
||||
semconv.ServerAddress(host),
|
||||
semconv.ServerPort(port),
|
||||
}
|
||||
}
|
||||
|
||||
// TrackExport tracks an export operation and returns an ExportOp to complete the tracking.
|
||||
func (em *Instrumentation) TrackExport(ctx context.Context, rm *metricpb.ResourceMetrics) ExportOp {
|
||||
if em == nil {
|
||||
return ExportOp{}
|
||||
}
|
||||
start := time.Now()
|
||||
|
||||
var dataPointCount int64
|
||||
inflightEnabled := em.inflight.Enabled(ctx)
|
||||
exportedEnabled := em.exported.Enabled(ctx)
|
||||
|
||||
if inflightEnabled || exportedEnabled {
|
||||
dataPointCount = countProtoDataPoints(rm)
|
||||
}
|
||||
|
||||
if inflightEnabled {
|
||||
em.inflight.Inst().Add(ctx, dataPointCount, em.addOpt)
|
||||
}
|
||||
|
||||
return ExportOp{
|
||||
ctx: ctx,
|
||||
start: start,
|
||||
dataPointCount: dataPointCount,
|
||||
inst: em,
|
||||
}
|
||||
}
|
||||
|
||||
// ExportOp tracks the operation being observed by [Instrumentation.TrackExport].
|
||||
type ExportOp struct {
|
||||
ctx context.Context
|
||||
start time.Time
|
||||
dataPointCount int64
|
||||
|
||||
inst *Instrumentation
|
||||
}
|
||||
|
||||
// End completes the observation of the operation being observed by a call to
|
||||
// [Instrumentation.TrackExport].
|
||||
//
|
||||
// Any error that is encountered is provided as err.
|
||||
func (e ExportOp) End(err error) {
|
||||
if e.inst == nil {
|
||||
return
|
||||
}
|
||||
if e.inst.inflight.Enabled(e.ctx) {
|
||||
e.inst.inflight.Inst().Add(e.ctx, -e.dataPointCount, e.inst.addOpt)
|
||||
}
|
||||
|
||||
success := successful(e.dataPointCount, err)
|
||||
// Record successfully exported data points, even if the value is 0 which are
|
||||
// meaningful to distribution aggregations.
|
||||
if e.inst.exported.Enabled(e.ctx) {
|
||||
e.inst.exported.Inst().Add(e.ctx, success, e.inst.addOpt)
|
||||
}
|
||||
|
||||
if err != nil && e.inst.exported.Enabled(e.ctx) {
|
||||
attrsPtr := attrPool.Get().(*[]attribute.KeyValue)
|
||||
defer func() {
|
||||
*attrsPtr = (*attrsPtr)[:0]
|
||||
attrPool.Put(attrsPtr)
|
||||
}()
|
||||
|
||||
*attrsPtr = append(*attrsPtr, e.inst.attrs...)
|
||||
*attrsPtr = append(*attrsPtr, semconv.ErrorType(err))
|
||||
|
||||
set := attribute.NewSet(*attrsPtr...)
|
||||
e.inst.exported.Inst().Add(e.ctx, e.dataPointCount-success, metric.WithAttributeSet(set))
|
||||
}
|
||||
|
||||
if e.inst.duration.Enabled(e.ctx) {
|
||||
d := time.Since(e.start).Seconds()
|
||||
if err != nil {
|
||||
recOptPtr := recOptPool.Get().(*[]metric.RecordOption)
|
||||
defer func() {
|
||||
*recOptPtr = (*recOptPtr)[:0]
|
||||
recOptPool.Put(recOptPtr)
|
||||
}()
|
||||
|
||||
attrsPtr := attrPool.Get().(*[]attribute.KeyValue)
|
||||
defer func() {
|
||||
*attrsPtr = (*attrsPtr)[:0]
|
||||
attrPool.Put(attrsPtr)
|
||||
}()
|
||||
|
||||
*attrsPtr = append(*attrsPtr, e.inst.attrs...)
|
||||
*attrsPtr = append(
|
||||
*attrsPtr,
|
||||
semconv.ErrorType(err),
|
||||
semconv.RPCResponseStatusCode(status.Code(err).String()),
|
||||
)
|
||||
|
||||
set := attribute.NewSet(*attrsPtr...)
|
||||
*recOptPtr = append(*recOptPtr, metric.WithAttributeSet(set))
|
||||
|
||||
e.inst.duration.Inst().Record(e.ctx, d, *recOptPtr...)
|
||||
} else {
|
||||
e.inst.duration.Inst().Record(e.ctx, d, e.inst.recOpt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// countProtoDataPoints counts the total number of data points in a ResourceMetrics.
|
||||
func countProtoDataPoints(rm *metricpb.ResourceMetrics) int64 {
|
||||
if rm == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
var total int64
|
||||
for _, sm := range rm.ScopeMetrics {
|
||||
for _, m := range sm.Metrics {
|
||||
switch data := m.Data.(type) {
|
||||
case *metricpb.Metric_Gauge:
|
||||
if data.Gauge != nil {
|
||||
total += int64(len(data.Gauge.DataPoints))
|
||||
}
|
||||
case *metricpb.Metric_Sum:
|
||||
if data.Sum != nil {
|
||||
total += int64(len(data.Sum.DataPoints))
|
||||
}
|
||||
case *metricpb.Metric_Histogram:
|
||||
if data.Histogram != nil {
|
||||
total += int64(len(data.Histogram.DataPoints))
|
||||
}
|
||||
case *metricpb.Metric_ExponentialHistogram:
|
||||
if data.ExponentialHistogram != nil {
|
||||
total += int64(len(data.ExponentialHistogram.DataPoints))
|
||||
}
|
||||
case *metricpb.Metric_Summary:
|
||||
if data.Summary != nil {
|
||||
total += int64(len(data.Summary.DataPoints))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// successful returns the number of successfully exported data points out of the n
|
||||
// that were exported based on the provided error.
|
||||
//
|
||||
// If err is nil, n is returned. All data points were successfully exported.
|
||||
//
|
||||
// If err is not nil and not an [internal.PartialSuccess] error, 0 is returned.
|
||||
// It is assumed all data points failed to be exported.
|
||||
//
|
||||
// If err is an [internal.PartialSuccess] error, the number of successfully
|
||||
// exported data points is computed by subtracting the RejectedItems field from n. If
|
||||
// RejectedItems is negative, n is returned. If RejectedItems is greater than
|
||||
// n, 0 is returned.
|
||||
func successful(n int64, err error) int64 {
|
||||
if err == nil {
|
||||
return n // All data points successfully exported.
|
||||
}
|
||||
// Split rejection calculation so successful is inlinable.
|
||||
return n - rejected(n, err)
|
||||
}
|
||||
|
||||
var errPartialPool = &sync.Pool{
|
||||
New: func() any { return new(internal.PartialSuccess) },
|
||||
}
|
||||
|
||||
// rejected returns how many out of the n data points exporter were rejected based on
|
||||
// the provided non-nil err.
|
||||
func rejected(n int64, err error) int64 {
|
||||
ps := errPartialPool.Get().(*internal.PartialSuccess)
|
||||
defer errPartialPool.Put(ps)
|
||||
// Check for partial success.
|
||||
if errors.As(err, ps) {
|
||||
// Bound RejectedItems to [0, n]. This should not be needed,
|
||||
// but be defensive as this is from an external source.
|
||||
return min(max(ps.RejectedItems, 0), n)
|
||||
}
|
||||
return n // All data points rejected.
|
||||
}
|
||||
Generated
Vendored
+143
@@ -0,0 +1,143 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/observ/target.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package observ // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/observ"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
schemeUnix = "unix"
|
||||
schemeUnixAbstract = "unix-abstract"
|
||||
)
|
||||
|
||||
// ParseCanonicalTarget parses a target string and returns the extracted host
|
||||
// (domain address or IP), the target port, or an error.
|
||||
//
|
||||
// If no port is specified, -1 is returned.
|
||||
//
|
||||
// If no host is specified, an empty string is returned.
|
||||
//
|
||||
// The target string is expected to always have the form
|
||||
// "<scheme>://[authority]/<endpoint>". For example:
|
||||
// - "dns:///example.com:42"
|
||||
// - "dns://8.8.8.8/example.com:42"
|
||||
// - "unix:///path/to/socket"
|
||||
// - "unix-abstract:///socket-name"
|
||||
// - "passthrough:///192.34.2.1:42"
|
||||
//
|
||||
// The target is expected to come from the CanonicalTarget method of a gRPC
|
||||
// Client.
|
||||
func ParseCanonicalTarget(target string) (string, int, error) {
|
||||
const sep = "://"
|
||||
|
||||
// Find scheme. Do not allocate the string by using url.Parse.
|
||||
idx := strings.Index(target, sep)
|
||||
if idx == -1 {
|
||||
return "", -1, fmt.Errorf("invalid target %q: missing scheme", target)
|
||||
}
|
||||
scheme, endpoint := target[:idx], target[idx+len(sep):]
|
||||
|
||||
// Check for unix schemes.
|
||||
if scheme == schemeUnix || scheme == schemeUnixAbstract {
|
||||
return parseUnix(endpoint)
|
||||
}
|
||||
|
||||
// Strip leading slash and any authority.
|
||||
if i := strings.Index(endpoint, "/"); i != -1 {
|
||||
endpoint = endpoint[i+1:]
|
||||
}
|
||||
|
||||
// DNS, passthrough, and custom resolvers.
|
||||
return parseEndpoint(endpoint)
|
||||
}
|
||||
|
||||
// parseUnix parses unix socket targets.
|
||||
func parseUnix(endpoint string) (string, int, error) {
|
||||
// Format: unix[-abstract]://path
|
||||
//
|
||||
// We should have "/path" (empty authority) if valid.
|
||||
if len(endpoint) >= 1 && endpoint[0] == '/' {
|
||||
// Return the full path including leading slash.
|
||||
return endpoint, -1, nil
|
||||
}
|
||||
|
||||
// If there's no leading slash, it means there might be an authority
|
||||
// Check for authority case (should error): "authority/path"
|
||||
if slashIdx := strings.Index(endpoint, "/"); slashIdx > 0 {
|
||||
return "", -1, fmt.Errorf("invalid (non-empty) authority: %s", endpoint[:slashIdx])
|
||||
}
|
||||
|
||||
return "", -1, errors.New("invalid unix target format")
|
||||
}
|
||||
|
||||
// parseEndpoint parses an endpoint from a gRPC target.
|
||||
//
|
||||
// It supports the following formats:
|
||||
// - "host"
|
||||
// - "host%zone"
|
||||
// - "host:port"
|
||||
// - "host%zone:port"
|
||||
// - "ipv4"
|
||||
// - "ipv4%zone"
|
||||
// - "ipv4:port"
|
||||
// - "ipv4%zone:port"
|
||||
// - "ipv6"
|
||||
// - "ipv6%zone"
|
||||
// - "[ipv6]"
|
||||
// - "[ipv6%zone]"
|
||||
// - "[ipv6]:port"
|
||||
// - "[ipv6%zone]:port"
|
||||
//
|
||||
// It returns the host or host%zone (domain address or IP), the port (or -1 if
|
||||
// not specified), or an error if the input is not a valid.
|
||||
func parseEndpoint(endpoint string) (string, int, error) {
|
||||
// First check if the endpoint is just an IP address.
|
||||
if ip := parseIP(endpoint); ip != "" {
|
||||
return ip, -1, nil
|
||||
}
|
||||
|
||||
// If there's no colon, there is no port (IPv6 with no port checked above).
|
||||
if !strings.Contains(endpoint, ":") {
|
||||
return endpoint, -1, nil
|
||||
}
|
||||
|
||||
host, portStr, err := net.SplitHostPort(endpoint)
|
||||
if err != nil {
|
||||
return "", -1, fmt.Errorf("invalid host:port %q: %w", endpoint, err)
|
||||
}
|
||||
|
||||
const base, bitSize = 10, 16
|
||||
port16, err := strconv.ParseUint(portStr, base, bitSize)
|
||||
if err != nil {
|
||||
return "", -1, fmt.Errorf("invalid port %q: %w", portStr, err)
|
||||
}
|
||||
port := int(port16) // port is guaranteed to be in the range [0, 65535].
|
||||
|
||||
return host, port, nil
|
||||
}
|
||||
|
||||
// parseIP attempts to parse the entire endpoint as an IP address.
|
||||
// It returns the normalized string form of the IP if successful,
|
||||
// or an empty string if parsing fails.
|
||||
func parseIP(ip string) string {
|
||||
// Strip leading and trailing brackets for IPv6 addresses.
|
||||
if len(ip) >= 2 && ip[0] == '[' && ip[len(ip)-1] == ']' {
|
||||
ip = ip[1 : len(ip)-1]
|
||||
}
|
||||
addr, err := netip.ParseAddr(ip)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// Return the normalized string form of the IP.
|
||||
return addr.String()
|
||||
}
|
||||
Generated
Vendored
+28
-15
@@ -35,6 +35,9 @@ const (
|
||||
// DefaultMetricsPath is a default URL path for endpoint that
|
||||
// receives metrics.
|
||||
DefaultMetricsPath string = "/v1/metrics"
|
||||
// DefaultMaxRequestSize is the default maximum size of a serialized export
|
||||
// request, before compression.
|
||||
DefaultMaxRequestSize int = 64 * 1024 * 1024
|
||||
// DefaultBackoff is a default base backoff time used in the
|
||||
// exponential backoff strategy.
|
||||
DefaultBackoff time.Duration = 300 * time.Millisecond
|
||||
@@ -49,13 +52,14 @@ type (
|
||||
HTTPTransportProxyFunc func(*http.Request) (*url.URL, error)
|
||||
|
||||
SignalConfig struct {
|
||||
Endpoint string
|
||||
Insecure bool
|
||||
TLSCfg *tls.Config
|
||||
Headers map[string]string
|
||||
Compression Compression
|
||||
Timeout time.Duration
|
||||
URLPath string
|
||||
Endpoint string
|
||||
Insecure bool
|
||||
TLSCfg *tls.Config
|
||||
Headers map[string]string
|
||||
Compression Compression
|
||||
MaxRequestSize int
|
||||
Timeout time.Duration
|
||||
URLPath string
|
||||
|
||||
TemporalitySelector metric.TemporalitySelector
|
||||
AggregationSelector metric.AggregationSelector
|
||||
@@ -87,10 +91,11 @@ type (
|
||||
func NewHTTPConfig(opts ...HTTPOption) Config {
|
||||
cfg := Config{
|
||||
Metrics: SignalConfig{
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorHTTPPort),
|
||||
URLPath: DefaultMetricsPath,
|
||||
Compression: NoCompression,
|
||||
Timeout: DefaultTimeout,
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorHTTPPort),
|
||||
URLPath: DefaultMetricsPath,
|
||||
Compression: NoCompression,
|
||||
MaxRequestSize: DefaultMaxRequestSize,
|
||||
Timeout: DefaultTimeout,
|
||||
|
||||
TemporalitySelector: metric.DefaultTemporalitySelector,
|
||||
AggregationSelector: metric.DefaultAggregationSelector,
|
||||
@@ -123,10 +128,11 @@ func cleanPath(urlPath string, defaultPath string) string {
|
||||
func NewGRPCConfig(opts ...GRPCOption) Config {
|
||||
cfg := Config{
|
||||
Metrics: SignalConfig{
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorGRPCPort),
|
||||
URLPath: DefaultMetricsPath,
|
||||
Compression: NoCompression,
|
||||
Timeout: DefaultTimeout,
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorGRPCPort),
|
||||
URLPath: DefaultMetricsPath,
|
||||
Compression: NoCompression,
|
||||
MaxRequestSize: DefaultMaxRequestSize,
|
||||
Timeout: DefaultTimeout,
|
||||
|
||||
TemporalitySelector: metric.DefaultTemporalitySelector,
|
||||
AggregationSelector: metric.DefaultAggregationSelector,
|
||||
@@ -354,6 +360,13 @@ func WithTimeout(duration time.Duration) GenericOption {
|
||||
})
|
||||
}
|
||||
|
||||
func WithMaxRequestSize(size int) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.MaxRequestSize = size
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithTemporalitySelector(selector metric.TemporalitySelector) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.TemporalitySelector = selector
|
||||
|
||||
Generated
Vendored
+18
@@ -81,6 +81,16 @@ func Value(v attribute.Value) *cpb.AnyValue {
|
||||
av.Value = &cpb.AnyValue_StringValue{
|
||||
StringValue: v.AsString(),
|
||||
}
|
||||
case attribute.BYTESLICE:
|
||||
av.Value = &cpb.AnyValue_BytesValue{
|
||||
BytesValue: v.AsByteSlice(),
|
||||
}
|
||||
case attribute.SLICE:
|
||||
av.Value = &cpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &cpb.ArrayValue{
|
||||
Values: attrValues(v.AsSlice()),
|
||||
},
|
||||
}
|
||||
case attribute.STRINGSLICE:
|
||||
av.Value = &cpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &cpb.ArrayValue{
|
||||
@@ -143,3 +153,11 @@ func stringSliceValues(vals []string) []*cpb.AnyValue {
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func attrValues(vals []attribute.Value) []*cpb.AnyValue {
|
||||
converted := make([]*cpb.AnyValue, len(vals))
|
||||
for i, v := range vals {
|
||||
converted[i] = Value(v)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
Generated
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
# Experimental Features
|
||||
|
||||
The OTLP gRPC metric exporter contains features that have not yet stabilized in the OpenTelemetry specification.
|
||||
These features are added to the OpenTelemetry Go OTLP exporters prior to stabilization in the specification so that users can start experimenting with them and provide feedback.
|
||||
|
||||
These features may change in backwards incompatible ways as feedback is applied.
|
||||
See the [Compatibility and Stability](#compatibility-and-stability) section for more information.
|
||||
|
||||
## Features
|
||||
|
||||
- [Self-Observability](#self-observability)
|
||||
|
||||
### Self-Observability
|
||||
|
||||
The OTLP gRPC metric exporter can emit self-observability metrics to track its own operation.
|
||||
|
||||
This experimental feature can be enabled by setting the `OTEL_GO_X_OBSERVABILITY` environment variable.
|
||||
The value must be the case-insensitive string of `"true"` to enable the feature.
|
||||
All other values are ignored.
|
||||
|
||||
When enabled, the exporter will emit the following metrics using the global MeterProvider:
|
||||
|
||||
- `otel.sdk.exporter.metric_data_point.exported`: Counter tracking successfully exported data points
|
||||
- `otel.sdk.exporter.metric_data_point.inflight`: UpDownCounter tracking data points currently being exported
|
||||
- `otel.sdk.exporter.operation.duration`: Histogram tracking export operation duration in seconds
|
||||
|
||||
All metrics include attributes identifying the exporter component and destination server:
|
||||
|
||||
- `otel.component.type`: Type of component (e.g., "otlp_grpc_metric_exporter")
|
||||
- `otel.component.name`: Unique component instance name (e.g., "otlp_grpc_metric_exporter/0")
|
||||
- `server.address`: Server hostname or address
|
||||
- `server.port`: Server port number
|
||||
|
||||
#### Examples
|
||||
|
||||
Enable self-observability metrics.
|
||||
|
||||
```console
|
||||
export OTEL_GO_X_OBSERVABILITY=true
|
||||
```
|
||||
|
||||
Disable self-observability metrics.
|
||||
|
||||
```console
|
||||
unset OTEL_GO_X_OBSERVABILITY
|
||||
```
|
||||
|
||||
## Compatibility and Stability
|
||||
|
||||
Experimental features do not fall within the scope of the OpenTelemetry Go versioning and stability [policy](../../../../../../VERSIONING.md).
|
||||
These features may be removed or modified in successive version releases, including patch versions.
|
||||
|
||||
When an experimental feature is promoted to a stable feature, a migration path will be included in the changelog entry of the release.
|
||||
There is no guarantee that any environment variable feature flags that enabled the experimental feature will be supported by the stable version.
|
||||
If they are supported, they may be accompanied with a deprecation notice stating a timeline for the removal of that support.
|
||||
Generated
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package x // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/x"
|
||||
|
||||
import "strings"
|
||||
|
||||
// Observability is an experimental feature flag that defines if OTLP
|
||||
// gRPC metric exporter should include self-observability metrics.
|
||||
//
|
||||
// To enable this feature set the OTEL_GO_X_OBSERVABILITY environment variable
|
||||
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
|
||||
// will also enable this).
|
||||
var Observability = newFeature(
|
||||
[]string{"OBSERVABILITY"},
|
||||
func(v string) (string, bool) {
|
||||
if strings.EqualFold(v, "true") {
|
||||
return v, true
|
||||
}
|
||||
return "", false
|
||||
},
|
||||
)
|
||||
Generated
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/x/x.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package x documents experimental features for [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc].
|
||||
package x // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/x"
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Feature is an experimental feature control flag. It provides a uniform way
|
||||
// to interact with these feature flags and parse their values.
|
||||
type Feature[T any] struct {
|
||||
keys []string
|
||||
parse func(v string) (T, bool)
|
||||
}
|
||||
|
||||
func newFeature[T any](suffix []string, parse func(string) (T, bool)) Feature[T] {
|
||||
const envKeyRoot = "OTEL_GO_X_"
|
||||
keys := make([]string, 0, len(suffix))
|
||||
for _, s := range suffix {
|
||||
keys = append(keys, envKeyRoot+s)
|
||||
}
|
||||
return Feature[T]{
|
||||
keys: keys,
|
||||
parse: parse,
|
||||
}
|
||||
}
|
||||
|
||||
// Keys returns the environment variable keys that can be set to enable the
|
||||
// feature.
|
||||
func (f Feature[T]) Keys() []string { return f.keys }
|
||||
|
||||
// Lookup returns the user configured value for the feature and true if the
|
||||
// user has enabled the feature. Otherwise, if the feature is not enabled, a
|
||||
// zero-value and false are returned.
|
||||
func (f Feature[T]) Lookup() (v T, ok bool) {
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/62effed618589a0bec416a87e559c0a9d96289bb/specification/configuration/sdk-environment-variables.md#parsing-empty-value
|
||||
//
|
||||
// > The SDK MUST interpret an empty value of an environment variable the
|
||||
// > same way as when the variable is unset.
|
||||
for _, key := range f.keys {
|
||||
vRaw := os.Getenv(key)
|
||||
if vRaw != "" {
|
||||
return f.parse(vRaw)
|
||||
}
|
||||
}
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// Enabled reports whether the feature is enabled.
|
||||
func (f Feature[T]) Enabled() bool {
|
||||
_, ok := f.Lookup()
|
||||
return ok
|
||||
}
|
||||
Generated
Vendored
+1
-1
@@ -5,5 +5,5 @@ package otlpmetricgrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlpme
|
||||
|
||||
// Version is the current release version of the OpenTelemetry OTLP over gRPC metrics exporter in use.
|
||||
func Version() string {
|
||||
return "1.43.0"
|
||||
return "1.44.0"
|
||||
}
|
||||
|
||||
Generated
Vendored
+18
@@ -87,6 +87,16 @@ func Value(v attribute.Value) *commonpb.AnyValue {
|
||||
av.Value = &commonpb.AnyValue_StringValue{
|
||||
StringValue: v.AsString(),
|
||||
}
|
||||
case attribute.BYTESLICE:
|
||||
av.Value = &commonpb.AnyValue_BytesValue{
|
||||
BytesValue: v.AsByteSlice(),
|
||||
}
|
||||
case attribute.SLICE:
|
||||
av.Value = &commonpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &commonpb.ArrayValue{
|
||||
Values: values(v.AsSlice()),
|
||||
},
|
||||
}
|
||||
case attribute.STRINGSLICE:
|
||||
av.Value = &commonpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &commonpb.ArrayValue{
|
||||
@@ -149,3 +159,11 @@ func stringSliceValues(vals []string) []*commonpb.AnyValue {
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func values(vals []attribute.Value) []*commonpb.AnyValue {
|
||||
converted := make([]*commonpb.AnyValue, len(vals))
|
||||
for i, v := range vals {
|
||||
converted[i] = Value(v)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
+34
-18
@@ -6,6 +6,7 @@ package otlptracegrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlptra
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal"
|
||||
@@ -26,11 +28,12 @@ import (
|
||||
)
|
||||
|
||||
type client struct {
|
||||
endpoint string
|
||||
dialOpts []grpc.DialOption
|
||||
metadata metadata.MD
|
||||
exportTimeout time.Duration
|
||||
requestFunc retry.RequestFunc
|
||||
endpoint string
|
||||
dialOpts []grpc.DialOption
|
||||
metadata metadata.MD
|
||||
exportTimeout time.Duration
|
||||
maxRequestSize int
|
||||
requestFunc retry.RequestFunc
|
||||
|
||||
// stopCtx is used as a parent context for all exports. Therefore, when it
|
||||
// is canceled with the stopFunc all exports are canceled.
|
||||
@@ -65,14 +68,15 @@ func newClient(opts ...Option) *client {
|
||||
ctx, cancel := context.WithCancel(context.Background()) //nolint:gosec // cancel called in client shutdown.
|
||||
|
||||
c := &client{
|
||||
endpoint: cfg.Traces.Endpoint,
|
||||
exportTimeout: cfg.Traces.Timeout,
|
||||
requestFunc: cfg.RetryConfig.RequestFunc(retryable),
|
||||
dialOpts: cfg.DialOptions,
|
||||
stopCtx: ctx,
|
||||
stopFunc: cancel,
|
||||
conn: cfg.GRPCConn,
|
||||
instID: counter.NextExporterID(),
|
||||
endpoint: cfg.Traces.Endpoint,
|
||||
exportTimeout: cfg.Traces.Timeout,
|
||||
maxRequestSize: cfg.Traces.MaxRequestSize,
|
||||
requestFunc: cfg.RetryConfig.RequestFunc(retryable),
|
||||
dialOpts: cfg.DialOptions,
|
||||
stopCtx: ctx,
|
||||
stopFunc: cancel,
|
||||
conn: cfg.GRPCConn,
|
||||
instID: counter.NextExporterID(),
|
||||
}
|
||||
|
||||
if len(cfg.Traces.Headers) > 0 {
|
||||
@@ -205,16 +209,28 @@ func (c *client) UploadTraces(ctx context.Context, protoSpans []*tracepb.Resourc
|
||||
ctx, cancel := c.exportContext(ctx)
|
||||
defer cancel()
|
||||
|
||||
var code codes.Code
|
||||
pbRequest := &coltracepb.ExportTraceServiceRequest{
|
||||
ResourceSpans: protoSpans,
|
||||
}
|
||||
|
||||
code := codes.Unknown
|
||||
if c.inst != nil {
|
||||
op := c.inst.ExportSpans(ctx, len(protoSpans))
|
||||
var spanCount int
|
||||
for _, rs := range protoSpans {
|
||||
for _, ss := range rs.ScopeSpans {
|
||||
spanCount += len(ss.Spans)
|
||||
}
|
||||
}
|
||||
op := c.inst.ExportSpans(ctx, spanCount)
|
||||
defer func() { op.End(uploadErr, code) }()
|
||||
}
|
||||
|
||||
if maxSize := c.maxRequestSize; maxSize > 0 && proto.Size(pbRequest) > maxSize {
|
||||
return fmt.Errorf("request message too large: exceeded %d bytes", maxSize)
|
||||
}
|
||||
|
||||
return c.requestFunc(ctx, func(iCtx context.Context) error {
|
||||
resp, err := c.tsc.Export(iCtx, &coltracepb.ExportTraceServiceRequest{
|
||||
ResourceSpans: protoSpans,
|
||||
})
|
||||
resp, err := c.tsc.Export(iCtx, pbRequest)
|
||||
if resp != nil && resp.PartialSuccess != nil {
|
||||
msg := resp.PartialSuccess.GetErrorMessage()
|
||||
n := resp.PartialSuccess.GetRejectedSpans()
|
||||
|
||||
Generated
Vendored
+7
-3
@@ -18,8 +18,8 @@ import (
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/x"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0/otelconv"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -72,6 +72,7 @@ var (
|
||||
func get[T any](p *sync.Pool) *[]T { return p.Get().(*[]T) }
|
||||
|
||||
func put[T any](p *sync.Pool, s *[]T) {
|
||||
clear(*s) // erase elements to allow GC to collect what they refer to.
|
||||
*s = (*s)[:0] // Reset.
|
||||
p.Put(s)
|
||||
}
|
||||
@@ -339,7 +340,10 @@ var errPartialPool = &sync.Pool{
|
||||
// the provided non-nil err.
|
||||
func rejected(n int64, err error) int64 {
|
||||
ps := errPartialPool.Get().(*internal.PartialSuccess)
|
||||
defer errPartialPool.Put(ps)
|
||||
defer func() {
|
||||
*ps = internal.PartialSuccess{} // erase fields to allow GC to collect them.
|
||||
errPartialPool.Put(ps)
|
||||
}()
|
||||
// Check for partial success.
|
||||
if errors.As(err, ps) {
|
||||
// Bound RejectedItems to [0, n]. This should not be needed,
|
||||
|
||||
Generated
Vendored
+28
-15
@@ -31,6 +31,9 @@ const (
|
||||
// DefaultTracesPath is a default URL path for endpoint that
|
||||
// receives spans.
|
||||
DefaultTracesPath string = "/v1/traces"
|
||||
// DefaultMaxRequestSize is the default maximum size of a serialized export
|
||||
// request, before compression.
|
||||
DefaultMaxRequestSize int = 64 * 1024 * 1024
|
||||
// DefaultTimeout is a default max waiting time for the backend to process
|
||||
// each span batch.
|
||||
DefaultTimeout time.Duration = 10 * time.Second
|
||||
@@ -42,13 +45,14 @@ type (
|
||||
HTTPTransportProxyFunc func(*http.Request) (*url.URL, error)
|
||||
|
||||
SignalConfig struct {
|
||||
Endpoint string
|
||||
Insecure bool
|
||||
TLSCfg *tls.Config
|
||||
Headers map[string]string
|
||||
Compression Compression
|
||||
Timeout time.Duration
|
||||
URLPath string
|
||||
Endpoint string
|
||||
Insecure bool
|
||||
TLSCfg *tls.Config
|
||||
Headers map[string]string
|
||||
Compression Compression
|
||||
MaxRequestSize int
|
||||
Timeout time.Duration
|
||||
URLPath string
|
||||
|
||||
// gRPC configurations
|
||||
GRPCCredentials credentials.TransportCredentials
|
||||
@@ -77,10 +81,11 @@ type (
|
||||
func NewHTTPConfig(opts ...HTTPOption) Config {
|
||||
cfg := Config{
|
||||
Traces: SignalConfig{
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorHTTPPort),
|
||||
URLPath: DefaultTracesPath,
|
||||
Compression: NoCompression,
|
||||
Timeout: DefaultTimeout,
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorHTTPPort),
|
||||
URLPath: DefaultTracesPath,
|
||||
Compression: NoCompression,
|
||||
MaxRequestSize: DefaultMaxRequestSize,
|
||||
Timeout: DefaultTimeout,
|
||||
},
|
||||
RetryConfig: retry.DefaultConfig,
|
||||
}
|
||||
@@ -111,10 +116,11 @@ func NewGRPCConfig(opts ...GRPCOption) Config {
|
||||
userAgent := "OTel OTLP Exporter Go/" + otlptrace.Version()
|
||||
cfg := Config{
|
||||
Traces: SignalConfig{
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorGRPCPort),
|
||||
URLPath: DefaultTracesPath,
|
||||
Compression: NoCompression,
|
||||
Timeout: DefaultTimeout,
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorGRPCPort),
|
||||
URLPath: DefaultTracesPath,
|
||||
Compression: NoCompression,
|
||||
MaxRequestSize: DefaultMaxRequestSize,
|
||||
Timeout: DefaultTimeout,
|
||||
},
|
||||
RetryConfig: retry.DefaultConfig,
|
||||
DialOptions: []grpc.DialOption{grpc.WithUserAgent(userAgent)},
|
||||
@@ -345,6 +351,13 @@ func WithTimeout(duration time.Duration) GenericOption {
|
||||
})
|
||||
}
|
||||
|
||||
func WithMaxRequestSize(size int) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Traces.MaxRequestSize = size
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithProxy(pf HTTPTransportProxyFunc) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Traces.Proxy = pf
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -5,4 +5,4 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/ot
|
||||
|
||||
// Version is the current release version of the OpenTelemetry OTLP gRPC trace
|
||||
// exporter in use.
|
||||
const Version = "1.43.0"
|
||||
const Version = "1.44.0"
|
||||
|
||||
Generated
Vendored
+10
@@ -192,6 +192,16 @@ func WithTimeout(duration time.Duration) Option {
|
||||
return wrappedOption{otlpconfig.WithTimeout(duration)}
|
||||
}
|
||||
|
||||
// WithMaxRequestSize sets the maximum size, in bytes, of a serialized export
|
||||
// request, before compression, that the exporter will send.
|
||||
//
|
||||
// If size is less than or equal to zero, no request-size limit is applied.
|
||||
// Disabling the limit is not recommended because it can lead to excessive
|
||||
// resource consumption or abuse.
|
||||
func WithMaxRequestSize(size int) Option {
|
||||
return wrappedOption{otlpconfig.WithMaxRequestSize(size)}
|
||||
}
|
||||
|
||||
// WithRetry sets the retry policy for transient retryable errors that may be
|
||||
// returned by the target endpoint when exporting a batch of spans.
|
||||
//
|
||||
|
||||
+1
-1
@@ -5,5 +5,5 @@ package otlptrace // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
|
||||
// Version is the current release version of the OpenTelemetry OTLP trace exporter in use.
|
||||
func Version() string {
|
||||
return "1.43.0"
|
||||
return "1.44.0"
|
||||
}
|
||||
|
||||
+9
@@ -51,6 +51,9 @@ type Float64ObservableCounterConfig struct {
|
||||
func NewFloat64ObservableCounterConfig(opts ...Float64ObservableCounterOption) Float64ObservableCounterConfig {
|
||||
var config Float64ObservableCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64ObservableCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -111,6 +114,9 @@ func NewFloat64ObservableUpDownCounterConfig(
|
||||
) Float64ObservableUpDownCounterConfig {
|
||||
var config Float64ObservableUpDownCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64ObservableUpDownCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -168,6 +174,9 @@ type Float64ObservableGaugeConfig struct {
|
||||
func NewFloat64ObservableGaugeConfig(opts ...Float64ObservableGaugeOption) Float64ObservableGaugeConfig {
|
||||
var config Float64ObservableGaugeConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64ObservableGauge(config)
|
||||
}
|
||||
return config
|
||||
|
||||
+9
@@ -50,6 +50,9 @@ type Int64ObservableCounterConfig struct {
|
||||
func NewInt64ObservableCounterConfig(opts ...Int64ObservableCounterOption) Int64ObservableCounterConfig {
|
||||
var config Int64ObservableCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64ObservableCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -110,6 +113,9 @@ func NewInt64ObservableUpDownCounterConfig(
|
||||
) Int64ObservableUpDownCounterConfig {
|
||||
var config Int64ObservableUpDownCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64ObservableUpDownCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -167,6 +173,9 @@ type Int64ObservableGaugeConfig struct {
|
||||
func NewInt64ObservableGaugeConfig(opts ...Int64ObservableGaugeOption) Int64ObservableGaugeConfig {
|
||||
var config Int64ObservableGaugeConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64ObservableGauge(config)
|
||||
}
|
||||
return config
|
||||
|
||||
+7
@@ -42,11 +42,18 @@ type MeterOption interface {
|
||||
applyMeter(MeterConfig) MeterConfig
|
||||
}
|
||||
|
||||
type experimentalOption interface {
|
||||
Experimental()
|
||||
}
|
||||
|
||||
// NewMeterConfig creates a new MeterConfig and applies
|
||||
// all the given options.
|
||||
func NewMeterConfig(opts ...MeterOption) MeterConfig {
|
||||
var config MeterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyMeter(config)
|
||||
}
|
||||
return config
|
||||
|
||||
+39
-12
@@ -24,10 +24,10 @@ all instruments fall into two overlapping logical categories: asynchronous or
|
||||
synchronous, and int64 or float64.
|
||||
|
||||
All synchronous instruments ([Int64Counter], [Int64UpDownCounter],
|
||||
[Int64Histogram], [Float64Counter], [Float64UpDownCounter], and
|
||||
[Float64Histogram]) are used to measure the operation and performance of source
|
||||
code during the source code execution. These instruments only make measurements
|
||||
when the source code they instrument is run.
|
||||
[Int64Histogram], [Int64Gauge], [Float64Counter], [Float64UpDownCounter],
|
||||
[Float64Histogram], and [Float64Gauge]) are used to measure the operation and
|
||||
performance of source code during the source code execution. These instruments
|
||||
only make measurements when the source code they instrument is run.
|
||||
|
||||
All asynchronous instruments ([Int64ObservableCounter],
|
||||
[Int64ObservableUpDownCounter], [Int64ObservableGauge],
|
||||
@@ -50,9 +50,11 @@ incrementally increase in value. UpDownCounters ([Int64UpDownCounter],
|
||||
values that can increase and decrease. When more information needs to be
|
||||
conveyed about all the synchronous measurements made during a collection cycle,
|
||||
a Histogram ([Int64Histogram] and [Float64Histogram]) should be used. Finally,
|
||||
when just the most recent measurement needs to be conveyed about an
|
||||
asynchronous measurement, a Gauge ([Int64ObservableGauge] and
|
||||
[Float64ObservableGauge]) should be used.
|
||||
when just the most recent measurement needs to be conveyed, a Gauge
|
||||
([Int64Gauge], [Float64Gauge], [Int64ObservableGauge], and
|
||||
[Float64ObservableGauge]) should be used: the synchronous variants record an
|
||||
instantaneous value at a specific point in code, while the observable variants
|
||||
sample the value via a callback once per collection cycle.
|
||||
|
||||
See the [OpenTelemetry documentation] for more information about instruments
|
||||
and their intended use.
|
||||
@@ -80,11 +82,11 @@ Measurements are made by recording values and information about the values with
|
||||
an instrument. How these measurements are recorded depends on the instrument.
|
||||
|
||||
Measurements for synchronous instruments ([Int64Counter], [Int64UpDownCounter],
|
||||
[Int64Histogram], [Float64Counter], [Float64UpDownCounter], and
|
||||
[Float64Histogram]) are recorded using the instrument methods directly. All
|
||||
counter instruments have an Add method that is used to measure an increment
|
||||
value, and all histogram instruments have a Record method to measure a data
|
||||
point.
|
||||
[Int64Histogram], [Int64Gauge], [Float64Counter], [Float64UpDownCounter],
|
||||
[Float64Histogram], and [Float64Gauge]) are recorded using the instrument
|
||||
methods directly. All counter instruments have an Add method that is used to
|
||||
measure an increment value, and all histogram and synchronous gauge
|
||||
instruments have a Record method to measure a data point.
|
||||
|
||||
Asynchronous instruments ([Int64ObservableCounter],
|
||||
[Int64ObservableUpDownCounter], [Int64ObservableGauge],
|
||||
@@ -107,6 +109,31 @@ respectively):
|
||||
If the criteria are not met, use the RegisterCallback method of the [Meter] that
|
||||
created the instrument to register a [Callback].
|
||||
|
||||
# Avoiding Expensive Computations
|
||||
|
||||
All synchronous instruments provide an Enabled method that reports whether the
|
||||
instrument will process measurements for the given context. When no SDK is
|
||||
registered or the instrument is otherwise disabled, Enabled returns false. This
|
||||
can be used to avoid expensive measurement work when a measurement will not be
|
||||
recorded:
|
||||
|
||||
if counter.Enabled(ctx) {
|
||||
counter.Add(ctx, 1, metric.WithAttributes(expensiveAttributes()...))
|
||||
}
|
||||
|
||||
This is especially valuable when computing attributes is expensive.
|
||||
[WithAttributes] performs non-trivial work on every call to build an
|
||||
[attribute.Set] from the provided attributes, and that work is wasted if the
|
||||
measurement is not recorded.
|
||||
|
||||
For performance sensitive code where the same attribute set is used repeatedly,
|
||||
prefer [WithAttributeSet]. It accepts a pre-built [attribute.Set], letting you
|
||||
pay the construction cost once and reuse it across many measurements:
|
||||
|
||||
attrs := attribute.NewSet(attribute.String("key", "val"))
|
||||
// ... later, on each call:
|
||||
counter.Add(ctx, 1, metric.WithAttributeSet(attrs))
|
||||
|
||||
# API Implementations
|
||||
|
||||
This package does not conform to the standard Go versioning policy, all of its
|
||||
|
||||
+33
-6
@@ -3,7 +3,9 @@
|
||||
|
||||
package metric // import "go.opentelemetry.io/otel/metric"
|
||||
|
||||
import "go.opentelemetry.io/otel/attribute"
|
||||
import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
// Observable is used as a grouping mechanism for all instruments that are
|
||||
// updated within a Callback.
|
||||
@@ -228,6 +230,9 @@ type AddConfig struct {
|
||||
func NewAddConfig(opts []AddOption) AddConfig {
|
||||
config := AddConfig{attrs: *attribute.EmptySet()}
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyAdd(config)
|
||||
}
|
||||
return config
|
||||
@@ -253,6 +258,9 @@ type RecordConfig struct {
|
||||
func NewRecordConfig(opts []RecordOption) RecordConfig {
|
||||
config := RecordConfig{attrs: *attribute.EmptySet()}
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyRecord(config)
|
||||
}
|
||||
return config
|
||||
@@ -278,6 +286,9 @@ type ObserveConfig struct {
|
||||
func NewObserveConfig(opts []ObserveOption) ObserveConfig {
|
||||
config := ObserveConfig{attrs: *attribute.EmptySet()}
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyObserve(config)
|
||||
}
|
||||
return config
|
||||
@@ -299,6 +310,10 @@ type attrOpt struct {
|
||||
set attribute.Set
|
||||
}
|
||||
|
||||
func (o *attrOpt) Set(set attribute.Set) {
|
||||
o.set = set
|
||||
}
|
||||
|
||||
// mergeSets returns the union of keys between a and b. Any duplicate keys will
|
||||
// use the value associated with b.
|
||||
func mergeSets(a, b attribute.Set) attribute.Set {
|
||||
@@ -311,7 +326,7 @@ func mergeSets(a, b attribute.Set) attribute.Set {
|
||||
return attribute.NewSet(merged...)
|
||||
}
|
||||
|
||||
func (o attrOpt) applyAdd(c AddConfig) AddConfig {
|
||||
func (o *attrOpt) applyAdd(c AddConfig) AddConfig {
|
||||
switch {
|
||||
case o.set.Len() == 0:
|
||||
case c.attrs.Len() == 0:
|
||||
@@ -322,7 +337,7 @@ func (o attrOpt) applyAdd(c AddConfig) AddConfig {
|
||||
return c
|
||||
}
|
||||
|
||||
func (o attrOpt) applyRecord(c RecordConfig) RecordConfig {
|
||||
func (o *attrOpt) applyRecord(c RecordConfig) RecordConfig {
|
||||
switch {
|
||||
case o.set.Len() == 0:
|
||||
case c.attrs.Len() == 0:
|
||||
@@ -333,7 +348,7 @@ func (o attrOpt) applyRecord(c RecordConfig) RecordConfig {
|
||||
return c
|
||||
}
|
||||
|
||||
func (o attrOpt) applyObserve(c ObserveConfig) ObserveConfig {
|
||||
func (o *attrOpt) applyObserve(c ObserveConfig) ObserveConfig {
|
||||
switch {
|
||||
case o.set.Len() == 0:
|
||||
case c.attrs.Len() == 0:
|
||||
@@ -350,8 +365,14 @@ func (o attrOpt) applyObserve(c ObserveConfig) ObserveConfig {
|
||||
// If multiple WithAttributeSet or WithAttributes options are passed the
|
||||
// attributes will be merged together in the order they are passed. Attributes
|
||||
// with duplicate keys will use the last value passed.
|
||||
//
|
||||
// Experimental: The returned option may implement
|
||||
// [go.opentelemetry.io/otel/metric/x.Settable][attribute.Set], which can be
|
||||
// used to replace the option's attribute set and reuse the option without
|
||||
// additional allocations. This behavior is experimental and may be changed or
|
||||
// removed in a future release without notice.
|
||||
func WithAttributeSet(attributes attribute.Set) MeasurementOption {
|
||||
return attrOpt{set: attributes}
|
||||
return &attrOpt{set: attributes}
|
||||
}
|
||||
|
||||
// WithAttributes converts attributes into an attribute Set and sets the Set to
|
||||
@@ -369,8 +390,14 @@ func WithAttributeSet(attributes attribute.Set) MeasurementOption {
|
||||
//
|
||||
// See [WithAttributeSet] for information about how multiple WithAttributes are
|
||||
// merged.
|
||||
//
|
||||
// Experimental: The returned option may implement
|
||||
// [go.opentelemetry.io/otel/metric/x.Settable][[]attribute.KeyValue], which can be
|
||||
// used to replace the option's attributes and reuse the option without
|
||||
// additional allocations. This behavior is experimental and may be changed or
|
||||
// removed in a future release without notice.
|
||||
func WithAttributes(attributes ...attribute.KeyValue) MeasurementOption {
|
||||
cp := make([]attribute.KeyValue, len(attributes))
|
||||
copy(cp, attributes)
|
||||
return attrOpt{set: attribute.NewSet(cp...)}
|
||||
return &attrOpt{set: attribute.NewSet(cp...)}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user