├── .codecov.yml ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── release.yml └── workflows │ ├── ci.yml │ ├── codeql-analysis.yml │ ├── dependency-review.yml │ ├── dockerhub-description.yml │ ├── f5-cla.yml │ ├── fossa.yml │ ├── labeler.yml │ ├── lint.yml │ ├── notifications.yml │ ├── scorecards.yml │ └── stale.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── .markdownlint-cli2.yaml ├── .pre-commit-config.yaml ├── .yamllint.yaml ├── CHANGELOG.md ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_LIFECYCLE.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── build └── Dockerfile ├── client ├── nginx.go └── nginx_test.go ├── collector ├── helper.go ├── helper_test.go ├── nginx.go └── nginx_plus.go ├── examples ├── basic_auth │ ├── README.md │ └── web-config.yml ├── kubernetes │ ├── README.md │ └── nginx-hello.yaml ├── systemd │ ├── README.md │ ├── nginx_exporter.service │ └── nginx_exporter.socket └── tls │ ├── README.md │ └── web-config.yml ├── exporter.go ├── exporter_test.go ├── go.mod ├── go.sum ├── grafana ├── README.md ├── dashboard.json └── dashboard.png ├── release-process.md ├── renovate.json └── scripts ├── completions.sh └── manpages.sh /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | informational: true 6 | comment: 7 | require_changes: true 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | indent_size = 4 9 | indent_style = tab 10 | 11 | [*.{md,yml,yaml}] 12 | indent_size = 2 13 | indent_style = space 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To reproduce** 11 | Steps to reproduce the behavior: 12 | 13 | 1. Deploy using 'some_command' 14 | 2. View logs '....' 15 | 3. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Your environment** 21 | 22 | - Version of the Prometheus exporter - release version or a specific commit 23 | - Version of Docker/Kubernetes 24 | - [if applicable] Kubernetes platform (e.g. Mini-kube or GCP) 25 | - Using NGINX or NGINX Plus 26 | 27 | **Additional context** 28 | Add any other context about the problem here. Any log files you want to share. 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: General inquiries 4 | url: https://github.com/nginx/nginx-prometheus-exporter/discussions 5 | about: Please use Discussions for all other questions. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Proposed changes 2 | 3 | Describe the use case and detail of the change. If this PR addresses an issue on GitHub, make sure to include a link to 4 | that issue here in this description (not in the title of the PR). 5 | 6 | ### Checklist 7 | 8 | Before creating a PR, run through this checklist and mark each as complete. 9 | 10 | - [ ] I have read the [CONTRIBUTING](https://github.com/nginx/nginx-prometheus-exporter/blob/main/CONTRIBUTING.md) guide 11 | - [ ] I have proven my fix is effective or that my feature works 12 | - [ ] I have checked that all unit tests pass after adding my changes 13 | - [ ] I have ensured the README is up to date 14 | - [ ] I have rebased my branch onto main 15 | - [ ] I will ensure my PR is targeting the main branch and pulling from my branch on my own fork 16 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - skip changelog 5 | categories: 6 | - title: 💣 Breaking Changes 7 | labels: 8 | - change 9 | - title: 🚀 Features 10 | labels: 11 | - enhancement 12 | - title: 🐛 Bug Fixes 13 | labels: 14 | - bug 15 | - title: 🧪 Tests 16 | labels: 17 | - tests 18 | - title: 🔨 Maintenance 19 | labels: 20 | - chore 21 | - title: 📝 Documentation 22 | labels: 23 | - documentation 24 | - title: ⬆️ Dependencies 25 | labels: 26 | - dependencies 27 | - title: Other Changes 28 | labels: 29 | - "*" 30 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - "v[0-9]+.[0-9]+.[0-9]+" 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | env: 14 | DOCKER_PLATFORMS: "linux/arm/v5,linux/arm/v6,linux/arm/v7,linux/arm64,linux/amd64,linux/ppc64le,linux/s390x,linux/mips64le,linux/386,linux/riscv64" 15 | 16 | concurrency: 17 | group: ${{ github.ref_name }}-ci 18 | cancel-in-progress: true 19 | 20 | permissions: 21 | contents: read 22 | 23 | jobs: 24 | unit-tests: 25 | name: Unit Tests 26 | runs-on: ubuntu-24.04 27 | steps: 28 | - name: Checkout Repository 29 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 30 | 31 | - name: Setup Golang Environment 32 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 33 | with: 34 | go-version: stable 35 | 36 | - name: Run Tests 37 | run: make test 38 | 39 | - name: Upload coverage reports to Codecov 40 | uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 41 | with: 42 | token: ${{ secrets.CODECOV_TOKEN }} 43 | 44 | build-docker: 45 | name: Build Docker Image 46 | runs-on: ubuntu-24.04 47 | permissions: 48 | contents: write # for lucacome/draft-release to create/update release draft 49 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 50 | id-token: write # for OIDC login to AWS ECR and goreleaser/goreleaser-action to sign artifacts 51 | packages: write # for docker/build-push-action to push to GHCR 52 | issues: write # for goreleaser/goreleaser-action to close milestones 53 | needs: unit-tests 54 | services: 55 | registry: 56 | image: registry:3 57 | ports: 58 | - 5000:5000 59 | steps: 60 | - name: Checkout Repository 61 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 62 | with: 63 | fetch-depth: 0 64 | 65 | - name: Setup Golang Environment 66 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 67 | with: 68 | go-version: stable 69 | 70 | - name: Setup QEMU 71 | uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 72 | 73 | - name: Docker Buildx 74 | uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 75 | with: 76 | version: latest 77 | driver-opts: network=host 78 | 79 | - name: DockerHub Login 80 | uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 81 | with: 82 | username: ${{ secrets.DOCKER_USERNAME }} 83 | password: ${{ secrets.DOCKER_PASSWORD }} 84 | if: github.event_name != 'pull_request' 85 | 86 | - name: Login to GitHub Container Registry 87 | uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 88 | with: 89 | registry: ghcr.io 90 | username: ${{ github.repository_owner }} 91 | password: ${{ secrets.GITHUB_TOKEN }} 92 | if: github.event_name != 'pull_request' 93 | 94 | - name: Configure AWS Credentials 95 | uses: aws-actions/configure-aws-credentials@b47578312673ae6fa5b5096b330d9fbac3d116df # v4.2.1 96 | with: 97 | aws-region: us-east-1 98 | role-to-assume: ${{ secrets.AWS_ROLE_PUBLIC_ECR }} 99 | if: github.event_name != 'pull_request' 100 | 101 | - name: Login to Public ECR 102 | uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 103 | with: 104 | registry: public.ecr.aws 105 | if: github.event_name != 'pull_request' 106 | 107 | - name: Login to Quay.io 108 | uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 109 | with: 110 | registry: quay.io 111 | username: ${{ secrets.QUAY_USERNAME }} 112 | password: ${{ secrets.QUAY_ROBOT_TOKEN }} 113 | if: github.event_name != 'pull_request' 114 | 115 | - name: Docker meta 116 | id: meta 117 | uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 118 | with: 119 | images: | 120 | name=nginx/nginx-prometheus-exporter,enable=${{ github.event_name != 'pull_request' }} 121 | name=ghcr.io/nginx/nginx-prometheus-exporter,enable=${{ github.event_name != 'pull_request' }} 122 | name=public.ecr.aws/nginx/nginx-prometheus-exporter,enable=${{ github.event_name != 'pull_request' }} 123 | name=quay.io/nginx/nginx-prometheus-exporter,enable=${{ github.event_name != 'pull_request' }} 124 | name=localhost:5000/nginx/nginx-prometheus-exporter 125 | tags: | 126 | type=edge 127 | type=ref,event=pr 128 | type=schedule 129 | type=semver,pattern={{version}} 130 | type=semver,pattern={{major}} 131 | type=semver,pattern={{major}}.{{minor}} 132 | labels: | 133 | org.opencontainers.image.vendor=NGINX Inc 134 | env: 135 | DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index 136 | 137 | - name: Create/Update Draft 138 | uses: lucacome/draft-release@f15262dc3ac8c3efbf09a8ce5406cd0fc47aabb1 # v1.2.2 139 | with: 140 | minor-label: "enhancement" 141 | major-label: "change" 142 | publish: ${{ github.ref_type == 'tag' }} 143 | collapse-after: 30 144 | notes-footer: | 145 | ## Upgrade 146 | 147 | - Use the {{version}} image from our [DockerHub](https://hub.docker.com/r/nginx/nginx-prometheus-exporter/tags?page=1&ordering=last_updated&name={{version-number}}), [GitHub Container](https://github.com/nginx/nginx-prometheus-exporter/pkgs/container/nginx-prometheus-exporter), [Amazon ECR Public Gallery](https://gallery.ecr.aws/nginx/nginx-prometheus-exporter) or [Quay.io](https://quay.io/repository/nginx/nginx-prometheus-exporter/tag/{{version-number}}?tab=tags). 148 | - Download the latest binaries from the [GitHub releases page](https://github.com/nginx/nginx-prometheus-exporter/releases/tag/{{version}}). 149 | - Update to the latest version with `brew upgrade nginx-prometheus-exporter`, `snap refresh nginx-prometheus-exporter` or `scoop update nginx-prometheus-exporter`. 150 | 151 | ## Compatibility 152 | 153 | - NGINX 0.1.18 or newer. 154 | - NGINX Plus R19 or newer. 155 | if: github.event_name != 'pull_request' 156 | 157 | - name: Download Syft 158 | uses: anchore/sbom-action/download-syft@9f7302141466aa6482940f15371237e9d9f4c34a # v0.19.0 159 | if: github.ref_type == 'tag' 160 | 161 | - name: Install Cosign 162 | uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2 163 | if: github.ref_type == 'tag' 164 | 165 | - name: Setup Snapcraft 166 | run: | 167 | sudo snap install snapcraft --classic 168 | mkdir -p $HOME/.cache/snapcraft/download 169 | mkdir -p $HOME/.cache/snapcraft/stage-packages 170 | if: github.ref_type == 'tag' 171 | 172 | - name: Install Nix 173 | uses: cachix/install-nix-action@526118121621777ccd86f79b04685a9319637641 # v31 174 | with: 175 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 176 | if: github.ref_type == 'tag' 177 | 178 | - name: Run GoReleaser 179 | uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 180 | with: 181 | version: v2.9.0 # renovate: datasource=github-tags depName=goreleaser/goreleaser 182 | args: ${{ github.ref_type == 'tag' && 'release' || 'build --snapshot' }} --clean 183 | env: 184 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 185 | NGINX_GITHUB_TOKEN: ${{ secrets.NGINX_PAT }} 186 | SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_LOGIN }} 187 | 188 | - name: Print NGINX Prometheus Exporter info 189 | run: ./dist/nginx-prometheus-exporter_linux_amd64_v1/nginx-prometheus-exporter --version 190 | continue-on-error: true 191 | 192 | - name: Build and Push Docker Image 193 | uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0 194 | with: 195 | file: build/Dockerfile 196 | context: "." 197 | target: goreleaser 198 | platforms: ${{ env.DOCKER_PLATFORMS }} 199 | tags: ${{ steps.meta.outputs.tags }} 200 | labels: ${{ steps.meta.outputs.labels }} 201 | annotations: ${{ steps.meta.outputs.annotations }} 202 | push: true 203 | cache-from: type=gha,scope=exporter 204 | cache-to: type=gha,scope=exporter,mode=max 205 | no-cache: ${{ github.event_name != 'pull_request' }} 206 | provenance: mode=max 207 | sbom: true 208 | 209 | - name: Scan image 210 | uses: anchore/scan-action@2c901ab7378897c01b8efaa2d0c9bf519cc64b9e # v6.2.0 211 | id: scan 212 | continue-on-error: true 213 | with: 214 | image: localhost:5000/nginx/nginx-prometheus-exporter:${{ steps.meta.outputs.version }} 215 | only-fixed: true 216 | add-cpes-if-none: true 217 | 218 | - name: Upload scan result to GitHub Security tab 219 | uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 220 | continue-on-error: true 221 | with: 222 | sarif_file: ${{ steps.scan.outputs.sarif }} 223 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: CodeQL 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | # The branches below must be a subset of the branches above 9 | branches: 10 | - main 11 | schedule: 12 | - cron: "24 0 * * 0" # run every Sunday at 00:24 13 | 14 | concurrency: 15 | group: ${{ github.ref_name }}-codeql 16 | cancel-in-progress: true 17 | 18 | permissions: 19 | contents: read 20 | 21 | jobs: 22 | analyze: 23 | name: Analyze 24 | runs-on: ubuntu-24.04 25 | permissions: 26 | actions: read # for github/codeql-action/init to get workflow details 27 | contents: read # for actions/checkout to fetch code 28 | security-events: write # for github/codeql-action/autobuild to send a status report 29 | packages: read # required to fetch internal or private CodeQL packs 30 | 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | include: 35 | - language: go 36 | build-mode: autobuild 37 | # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' 38 | # Use `c-cpp` to analyze code written in C, C++ or both 39 | # Use 'java-kotlin' to analyze code written in Java, Kotlin or both 40 | # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 41 | # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, 42 | # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. 43 | # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how 44 | # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages 45 | 46 | steps: 47 | - name: Checkout repository 48 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 49 | 50 | - name: Setup Golang Environment 51 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 52 | with: 53 | go-version: stable 54 | if: matrix.language == 'go' 55 | 56 | # Initializes the CodeQL tools for scanning. 57 | - name: Initialize CodeQL 58 | uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 59 | with: 60 | languages: ${{ matrix.language }} 61 | build-mode: ${{ matrix.build-mode }} 62 | # If you wish to specify custom queries, you can do so here or in a config file. 63 | # By default, queries listed here will override any specified in a config file. 64 | # Prefix the list here with "+" to use these queries and those in the config file. 65 | 66 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 67 | # queries: security-extended,security-and-quality 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 71 | with: 72 | category: "/language:${{matrix.language}}" 73 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | name: Dependency Review 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | 7 | concurrency: 8 | group: ${{ github.ref_name }}-deps-review 9 | cancel-in-progress: true 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | dependency-review: 16 | runs-on: ubuntu-24.04 17 | permissions: 18 | contents: read # for actions/checkout 19 | pull-requests: write # for actions/dependency-review-action to post comments 20 | steps: 21 | - name: Checkout Repository 22 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 23 | 24 | - name: Dependency Review 25 | uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1 26 | with: 27 | config-file: "nginx/k8s-common/dependency-review-config.yml@main" 28 | -------------------------------------------------------------------------------- /.github/workflows/dockerhub-description.yml: -------------------------------------------------------------------------------- 1 | name: Update Docker Hub Description 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - README.md 8 | - .github/workflows/dockerhub-description.yml 9 | 10 | concurrency: 11 | group: ${{ github.ref_name }}-dockerhub 12 | cancel-in-progress: true 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | dockerHubDescription: 19 | runs-on: ubuntu-24.04 20 | if: ${{ github.event.repository.fork == false }} 21 | steps: 22 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 23 | 24 | - name: Modify readme for DockerHub 25 | run: | 26 | sed -i '1,2d' README.md 27 | sed -i 's/(LICENSE)/(https:\/\/github.com\/nginx\/nginx-prometheus-exporter\/blob\/main\/LICENSE)/' README.md 28 | 29 | - name: Docker Hub Description 30 | uses: peter-evans/dockerhub-description@432a30c9e07499fd01da9f8a49f0faf9e0ca5b77 # v4.0.2 31 | with: 32 | username: ${{ secrets.DOCKER_USERNAME }} 33 | password: ${{ secrets.DOCKER_PASSWORD }} 34 | repository: nginx/nginx-prometheus-exporter 35 | short-description: ${{ github.event.repository.description }} 36 | -------------------------------------------------------------------------------- /.github/workflows/f5-cla.yml: -------------------------------------------------------------------------------- 1 | name: F5 CLA 2 | 3 | on: 4 | issue_comment: 5 | types: 6 | - created 7 | pull_request_target: 8 | types: 9 | - opened 10 | - synchronize 11 | - reopened 12 | 13 | concurrency: 14 | group: ${{ github.ref_name }}-cla 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | f5-cla: 21 | name: F5 CLA 22 | runs-on: ubuntu-24.04 23 | permissions: 24 | actions: write 25 | contents: read 26 | pull-requests: write 27 | statuses: write 28 | steps: 29 | - name: Run F5 Contributor License Agreement (CLA) assistant 30 | if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have hereby read the F5 CLA and agree to its terms') || github.event_name == 'pull_request_target' 31 | uses: contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1 32 | with: 33 | # Any pull request targeting the following branch will trigger a CLA check. 34 | branch: "main" 35 | # Path to the CLA document. 36 | path-to-document: "https://github.com/f5/.github/blob/main/CLA/cla-markdown.md" 37 | # Custom CLA messages. 38 | custom-notsigned-prcomment: "🎉 Thank you for your contribution! It appears you have not yet signed the F5 Contributor License Agreement (CLA), which is required for your changes to be incorporated into an F5 Open Source Software (OSS) project. Please kindly read the [F5 CLA](https://github.com/f5/.github/blob/main/CLA/cla-markdown.md) and reply on a new comment with the following text to agree:" 39 | custom-pr-sign-comment: "I have hereby read the F5 CLA and agree to its terms" 40 | custom-allsigned-prcomment: "✅ All required contributors have signed the F5 CLA for this PR. Thank you!" 41 | # Remote repository storing CLA signatures. 42 | remote-organization-name: "f5" 43 | remote-repository-name: "f5-cla-data" 44 | path-to-signatures: "signatures/beta/signatures.json" 45 | # Comma separated list of usernames for maintainers or any other individuals who should not be prompted for a CLA. 46 | allowlist: bot* 47 | # Do not lock PRs after a merge. 48 | lock-pullrequest-aftermerge: false 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | PERSONAL_ACCESS_TOKEN: ${{ secrets.F5_CLA_TOKEN }} 52 | -------------------------------------------------------------------------------- /.github/workflows/fossa.yml: -------------------------------------------------------------------------------- 1 | name: Fossa 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - "**.md" 9 | - "LICENSE" 10 | 11 | concurrency: 12 | group: ${{ github.ref_name }}-fossa 13 | cancel-in-progress: true 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | scan: 20 | name: Fossa 21 | runs-on: ubuntu-24.04 22 | if: ${{ github.event.repository.fork == false }} 23 | steps: 24 | - name: Checkout Repository 25 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 26 | 27 | - name: Scan 28 | uses: fossas/fossa-action@c0a7d013f84c8ee5e910593186598625513cc1e4 # v1.6.0 29 | with: 30 | api-key: ${{ secrets.FOSSA_TOKEN }} 31 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: "Pull Request Labeler" 2 | on: 3 | - pull_request_target 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | triage: 10 | permissions: 11 | contents: read 12 | pull-requests: write 13 | runs-on: ubuntu-24.04 14 | steps: 15 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 16 | with: 17 | sparse-checkout: | 18 | labeler.yml 19 | sparse-checkout-cone-mode: false 20 | repository: nginx/k8s-common 21 | 22 | - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0 23 | with: 24 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 25 | sync-labels: true 26 | configuration-path: labeler.yml 27 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | defaults: 9 | run: 10 | shell: bash 11 | 12 | concurrency: 13 | group: ${{ github.ref_name }}-lint 14 | cancel-in-progress: true 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | lint: 21 | name: Go Lint 22 | runs-on: ubuntu-24.04 23 | steps: 24 | - name: Checkout Repository 25 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 26 | 27 | - name: Setup Golang Environment 28 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 29 | with: 30 | go-version-file: go.mod 31 | 32 | - name: Lint Go 33 | uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 34 | with: 35 | version: v2.1.6 # renovate: datasource=github-tags depName=golangci/golangci-lint 36 | 37 | actionlint: 38 | name: Actionlint 39 | runs-on: ubuntu-24.04 40 | steps: 41 | - name: Checkout Repository 42 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 43 | 44 | - name: Lint Actions 45 | uses: reviewdog/action-actionlint@a5524e1c19e62881d79c1f1b9b6f09f16356e281 # v1.65.2 46 | with: 47 | actionlint_flags: -shellcheck "" 48 | 49 | markdown-lint: 50 | name: Markdown Lint 51 | runs-on: ubuntu-24.04 52 | steps: 53 | - name: Checkout Repository 54 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 55 | 56 | - name: Lint Markdown 57 | uses: DavidAnson/markdownlint-cli2-action@05f32210e84442804257b2a6f20b273450ec8265 # v19.1.0 58 | with: 59 | config: .markdownlint-cli2.yaml 60 | globs: "**/*.md" 61 | fix: false 62 | 63 | yaml-lint: 64 | name: YAML lint 65 | runs-on: ubuntu-24.04 66 | steps: 67 | - name: Checkout Repository 68 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 69 | 70 | - name: Lint YAML 71 | uses: reviewdog/action-yamllint@f01d8a48fd8d89f89895499fca2cff09f9e9e8c0 # v1.21.0 72 | -------------------------------------------------------------------------------- /.github/workflows/notifications.yml: -------------------------------------------------------------------------------- 1 | name: Notification 2 | 3 | on: 4 | workflow_run: 5 | branches: [main] 6 | workflows: 7 | - "CI" 8 | - "CodeQL" 9 | - "Fossa" 10 | - "Lint" 11 | types: 12 | - completed 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | on-failure: 19 | runs-on: ubuntu-24.04 20 | if: ${{ github.event.workflow_run.conclusion == 'failure' }} 21 | permissions: 22 | contents: read 23 | actions: read # for 8398a7/action-slack 24 | steps: 25 | - name: Data 26 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 27 | continue-on-error: true 28 | id: data 29 | with: 30 | script: | 31 | const message = context.payload.workflow_run.head_commit.message 32 | message_sanitized = message.split('\n')[0] 33 | 34 | const check_data = (await github.rest.checks.listForRef({ 35 | owner: context.payload.repository.owner.login, 36 | repo: context.payload.repository.name, 37 | ref: context.payload.workflow_run.head_commit.id, 38 | })).data.check_runs.filter(check_run => check_run.conclusion === 'failure')[0] 39 | 40 | return { 41 | job_name: check_data.name, 42 | job_url: check_data.html_url, 43 | commit_message: message_sanitized, 44 | } 45 | 46 | - name: Send Notification 47 | uses: 8398a7/action-slack@1750b5085f3ec60384090fb7c52965ef822e869e # v3.18.0 48 | with: 49 | status: custom 50 | custom_payload: | 51 | { 52 | username: 'GitHub', 53 | icon_emoji: ':github:', 54 | mention: 'channel', 55 | attachments: [{ 56 | title: '[${{ github.event.repository.full_name }}] ${{ github.event.workflow.name }} pipeline has failed (${{ github.event.workflow_run.event }})', 57 | color: 'danger', 58 | fields: [{ 59 | title: 'Commit', 60 | value: ``, 61 | short: true 62 | }, 63 | { 64 | title: 'Failed Job', 65 | value: `<${{ fromJSON(steps.data.outputs.result).job_url }}|${{ fromJSON(steps.data.outputs.result).job_name }}>`, 66 | short: true 67 | }, 68 | { 69 | title: 'Author', 70 | value: `${{ github.event.workflow_run.head_commit.author.name }}`, 71 | short: true 72 | }, 73 | { 74 | title: 'Pipeline URL', 75 | value: ``, 76 | short: true 77 | }] 78 | }] 79 | } 80 | env: 81 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} 82 | -------------------------------------------------------------------------------- /.github/workflows/scorecards.yml: -------------------------------------------------------------------------------- 1 | name: OpenSSF Scorecards 2 | on: 3 | branch_protection_rule: # yamllint disable-line rule:empty-values 4 | schedule: 5 | - cron: "29 22 * * 5" # run every Friday at 22:29 UTC 6 | push: 7 | branches: 8 | - main 9 | 10 | # Declare default permissions as read only. 11 | permissions: read-all 12 | 13 | jobs: 14 | analysis: 15 | name: Scorecard analysis 16 | runs-on: ubuntu-24.04 17 | permissions: 18 | # Needed to upload the results to code-scanning dashboard. 19 | security-events: write 20 | # Needed to publish results and get a badge (see publish_results below). 21 | id-token: write 22 | # Uncomment the permissions below if installing in a private repository. 23 | # contents: read 24 | # actions: read 25 | 26 | steps: 27 | - name: "Checkout code" 28 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 29 | with: 30 | persist-credentials: false 31 | 32 | - name: "Run analysis" 33 | uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 34 | with: 35 | results_file: results.sarif 36 | results_format: sarif 37 | repo_token: ${{ secrets.SCORECARD_READ_TOKEN }} 38 | 39 | # Public repositories: 40 | # - Publish results to OpenSSF REST API for easy access by consumers 41 | # - Allows the repository to include the Scorecard badge. 42 | # - See https://github.com/ossf/scorecard-action#publishing-results. 43 | # For private repositories: 44 | # - `publish_results` will always be set to `false`, regardless 45 | # of the value entered here. 46 | publish_results: true 47 | 48 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 49 | # format to the repository Actions tab. 50 | - name: "Upload artifact" 51 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 52 | with: 53 | name: SARIF file 54 | path: results.sarif 55 | retention-days: 5 56 | 57 | # Upload the results to GitHub's code scanning dashboard. 58 | - name: "Upload to code-scanning" 59 | uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 60 | with: 61 | sarif_file: results.sarif 62 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: "Close stale issues and PRs" 2 | on: 3 | schedule: 4 | - cron: "30 1 * * *" # run at 1:30am every day 5 | 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | stale: 11 | permissions: 12 | issues: write # for actions/stale to close stale issues 13 | pull-requests: write # for actions/stale to close stale PRs 14 | runs-on: ubuntu-24.04 15 | steps: 16 | - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 17 | with: 18 | repo-token: ${{ secrets.GITHUB_TOKEN }} 19 | stale-issue-message: "This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 10 days." 20 | stale-pr-message: "This PR is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 10 days." 21 | close-issue-message: "This issue was closed because it has been stalled for 10 days with no activity." 22 | close-pr-message: "This PR was closed because it has been stalled for 10 days with no activity." 23 | stale-issue-label: "stale" 24 | stale-pr-label: "stale" 25 | exempt-all-assignees: true 26 | exempt-all-issue-milestones: true 27 | exempt-issue-labels: "proposal, backlog, backlog candidate, epic" 28 | operations-per-run: 100 29 | days-before-stale: 90 30 | days-before-close: 10 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # NGINX Plus license files 2 | *.crt 3 | *.key 4 | 5 | # Visual Studio Code settings 6 | .vscode 7 | 8 | # the binary 9 | nginx-prometheus-exporter 10 | dist/ 11 | 12 | completions/ 13 | manpages/ 14 | 15 | coverage.txt 16 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: none 4 | enable: 5 | - asasalint 6 | - asciicheck 7 | - bidichk 8 | - containedctx 9 | - contextcheck 10 | - copyloopvar 11 | - dupword 12 | - durationcheck 13 | - errcheck 14 | - errchkjson 15 | - errname 16 | - errorlint 17 | - fatcontext 18 | - forcetypeassert 19 | - gocheckcompilerdirectives 20 | - gochecksumtype 21 | - gocritic 22 | - godot 23 | - gosec 24 | - gosmopolitan 25 | - govet 26 | - ineffassign 27 | - intrange 28 | - loggercheck 29 | - makezero 30 | - mirror 31 | - misspell 32 | - musttag 33 | - nilerr 34 | - nilnil 35 | - noctx 36 | - nolintlint 37 | - paralleltest 38 | - perfsprint 39 | - prealloc 40 | - predeclared 41 | - promlinter 42 | - reassign 43 | - revive 44 | - sloglint 45 | - staticcheck 46 | - tagalign 47 | - thelper 48 | - tparallel 49 | - unconvert 50 | - unparam 51 | - unused 52 | - usestdlibvars 53 | - wastedassign 54 | - whitespace 55 | - wrapcheck 56 | settings: 57 | govet: 58 | enable-all: true 59 | misspell: 60 | locale: US 61 | revive: 62 | rules: 63 | - name: blank-imports 64 | - name: constant-logical-expr 65 | - name: context-as-argument 66 | - name: context-keys-type 67 | - name: defer 68 | - name: dot-imports 69 | - name: duplicated-imports 70 | - name: empty-block 71 | - name: error-naming 72 | - name: error-return 73 | - name: error-strings 74 | - name: errorf 75 | - name: exported 76 | - name: import-shadowing 77 | - name: increment-decrement 78 | - name: indent-error-flow 79 | - name: package-comments 80 | - name: range 81 | - name: range-val-address 82 | - name: range-val-in-closure 83 | - name: receiver-naming 84 | - name: redefines-builtin-id 85 | - name: string-of-int 86 | - name: superfluous-else 87 | - name: time-naming 88 | - name: unchecked-type-assertion 89 | - name: unexported-return 90 | - name: unnecessary-stmt 91 | - name: unreachable-code 92 | - name: unused-parameter 93 | - name: var-declaration 94 | - name: var-naming 95 | sloglint: 96 | static-msg: true 97 | key-naming-case: snake 98 | exclusions: 99 | generated: lax 100 | presets: 101 | - comments 102 | - common-false-positives 103 | - legacy 104 | - std-error-handling 105 | paths: 106 | - third_party$ 107 | - builtin$ 108 | - examples$ 109 | issues: 110 | max-issues-per-linter: 0 111 | max-same-issues: 0 112 | formatters: 113 | enable: 114 | - gofmt 115 | - gofumpt 116 | - goimports 117 | exclusions: 118 | generated: lax 119 | paths: 120 | - third_party$ 121 | - builtin$ 122 | - examples$ 123 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | before: 3 | hooks: 4 | - ./scripts/completions.sh 5 | - ./scripts/manpages.sh 6 | 7 | builds: 8 | - env: 9 | - CGO_ENABLED=0 10 | goos: 11 | - darwin 12 | - freebsd 13 | - linux 14 | - solaris 15 | - windows 16 | goarch: 17 | - 386 18 | - amd64 19 | - arm 20 | - arm64 21 | - mips64 22 | - mips64le 23 | - ppc64 24 | - ppc64le 25 | - riscv64 26 | - s390x 27 | goarm: 28 | - 5 29 | - 6 30 | - 7 31 | gomips: 32 | - softfloat 33 | ignore: 34 | - goos: windows 35 | goarch: arm 36 | flags: 37 | - -trimpath 38 | ldflags: 39 | - "-s -w -X github.com/prometheus/common/version.Version={{.Version}} -X github.com/prometheus/common/version.BuildDate={{.Date}} -X github.com/prometheus/common/version.Branch={{.Branch}} -X github.com/prometheus/common/version.BuildUser=goreleaser" 40 | 41 | changelog: 42 | disable: true 43 | 44 | archives: 45 | - format_overrides: 46 | - goos: windows 47 | format: zip 48 | files: 49 | - README.md 50 | - LICENSE 51 | - completions/* 52 | - manpages/* 53 | 54 | sboms: 55 | - artifacts: archive 56 | documents: 57 | - "${artifact}.spdx.json" 58 | 59 | brews: 60 | - repository: 61 | owner: nginx 62 | name: homebrew-tap 63 | token: "{{ .Env.NGINX_GITHUB_TOKEN }}" 64 | directory: Formula 65 | homepage: https://github.com/nginx/nginx-prometheus-exporter 66 | description: NGINX Prometheus Exporter for NGINX and NGINX Plus 67 | license: Apache-2.0 68 | commit_author: 69 | name: nginx-bot 70 | email: integrations@nginx.com 71 | extra_install: |- 72 | bash_completion.install "completions/nginx-prometheus-exporter.bash" => "nginx-prometheus-exporter" 73 | zsh_completion.install "completions/nginx-prometheus-exporter.zsh" => "_nginx-prometheus-exporter" 74 | man1.install "manpages/nginx-prometheus-exporter.1.gz" 75 | 76 | signs: 77 | - cmd: cosign 78 | artifacts: checksum 79 | output: true 80 | certificate: "${artifact}.pem" 81 | args: 82 | - sign-blob 83 | - "--output-signature=${signature}" 84 | - "--output-certificate=${certificate}" 85 | - "${artifact}" 86 | - "--yes" 87 | 88 | milestones: 89 | - close: true 90 | 91 | snapcrafts: 92 | - name_template: "{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" 93 | title: NGINX Prometheus Exporter 94 | summary: NGINX Prometheus Exporter for NGINX and NGINX Plus 95 | description: | 96 | NGINX Prometheus exporter fetches the metrics from NGINX or NGINX Plus, 97 | converts the metrics into appropriate Prometheus metrics types and finally exposes 98 | them via an HTTP server to be collected by Prometheus. 99 | grade: stable 100 | confinement: strict 101 | publish: true 102 | license: "Apache-2.0" 103 | apps: 104 | nginx-prometheus-exporter: 105 | command: nginx-prometheus-exporter 106 | plugs: ["network", "network-bind"] 107 | completer: completions/nginx-prometheus-exporter.bash 108 | disable: "{{ if .IsSnapshot }}true{{ end }}" 109 | 110 | nix: 111 | - name: nginx-prometheus-exporter 112 | repository: 113 | owner: nginx 114 | name: nur 115 | token: "{{ .Env.NGINX_GITHUB_TOKEN }}" 116 | homepage: https://github.com/nginx/nginx-prometheus-exporter 117 | description: NGINX Prometheus Exporter for NGINX and NGINX Plus 118 | license: asl20 119 | commit_author: 120 | name: nginx-bot 121 | email: integrations@nginx.com 122 | extra_install: |- 123 | installManPage ./manpages/nginx-prometheus-exporter.1.gz 124 | installShellCompletion ./completions/* 125 | 126 | winget: 127 | - name: nginx-prometheus-exporter 128 | publisher: nginx 129 | license: Apache-2.0 130 | homepage: https://github.com/nginx/nginx-prometheus-exporter 131 | short_description: NGINX Prometheus Exporter for NGINX and NGINX Plus 132 | repository: 133 | owner: nginx 134 | name: winget-pkgs 135 | token: "{{ .Env.NGINX_GITHUB_TOKEN }}" 136 | branch: "nginx-prometheus-exporter-{{.Version}}" 137 | pull_request: 138 | enabled: true 139 | draft: true 140 | base: 141 | owner: microsoft 142 | name: winget-pkgs 143 | branch: master 144 | 145 | scoops: 146 | - repository: 147 | owner: nginx 148 | name: scoop-bucket 149 | token: "{{ .Env.NGINX_GITHUB_TOKEN }}" 150 | directory: bucket 151 | homepage: https://github.com/nginx/nginx-prometheus-exporter 152 | description: NGINX Prometheus Exporter for NGINX and NGINX Plus 153 | license: Apache-2.0 154 | commit_author: 155 | name: nginx-bot 156 | email: integrations@nginx.com 157 | -------------------------------------------------------------------------------- /.markdownlint-cli2.yaml: -------------------------------------------------------------------------------- 1 | # Rule configuration. 2 | # For rule descriptions and how to fix: https://github.com/DavidAnson/markdownlint/tree/main#rules--aliases 3 | config: 4 | ul-style: 5 | style: dash 6 | no-duplicate-heading: 7 | siblings_only: true 8 | line-length: 9 | line_length: 120 10 | code_blocks: false 11 | tables: false 12 | 13 | # Define glob expressions to ignore 14 | ignores: 15 | - ".github/" 16 | 17 | # Fix any fixable errors 18 | fix: true 19 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v5.0.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-yaml 10 | args: [--allow-multiple-documents] 11 | - id: check-ast 12 | - id: check-added-large-files 13 | - id: check-merge-conflict 14 | - id: check-shebang-scripts-are-executable 15 | - id: check-executables-have-shebangs 16 | - id: check-symlinks 17 | - id: check-case-conflict 18 | - id: check-vcs-permalinks 19 | - id: check-json 20 | - id: pretty-format-json 21 | args: [--autofix, --no-ensure-ascii] 22 | - id: mixed-line-ending 23 | args: [--fix=lf] 24 | - id: no-commit-to-branch 25 | - id: requirements-txt-fixer 26 | - id: fix-byte-order-marker 27 | 28 | - repo: https://github.com/golangci/golangci-lint 29 | rev: v2.1.6 30 | hooks: 31 | - id: golangci-lint-full 32 | 33 | - repo: https://github.com/gitleaks/gitleaks 34 | rev: v8.26.0 35 | hooks: 36 | - id: gitleaks 37 | 38 | - repo: https://github.com/DavidAnson/markdownlint-cli2 39 | rev: v0.17.2 40 | hooks: 41 | - id: markdownlint-cli2 42 | 43 | - repo: https://github.com/adrienverge/yamllint.git 44 | rev: v1.37.1 45 | hooks: 46 | - id: yamllint 47 | 48 | - repo: https://github.com/thlorenz/doctoc 49 | rev: v2.2.0 50 | hooks: 51 | - id: doctoc 52 | args: [--update-only, --title, "## Table of Contents"] 53 | 54 | ci: 55 | skip: [golangci-lint-full] 56 | autoupdate_schedule: quarterly # We use renovate for more frequent updates and there's no way to disable autoupdate 57 | -------------------------------------------------------------------------------- /.yamllint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ignore-from-file: .gitignore 3 | 4 | extends: default 5 | 6 | rules: 7 | comments: 8 | min-spaces-from-content: 1 9 | comments-indentation: enable 10 | document-start: disable 11 | empty-values: enable 12 | line-length: 13 | max: 120 14 | ignore: | 15 | .goreleaser.yml 16 | .github/ 17 | truthy: 18 | check-keys: false 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Starting with version 0.9.0 an automatically generated list of changes can be found on the [GitHub Releases page](https://github.com/nginx/nginx-prometheus-exporter/releases). 4 | 5 | ## 0.8.0 6 | 7 | CHANGES: 8 | 9 | - [103](https://github.com/nginx/nginx-prometheus-exporter/pull/103): Switch to `gcr.io/distroless/static` image. Use 10 | a non-root user to run the exporter process by default. Thanks to [Alex SZAKALY](https://github.com/alex1989hu). 11 | - Update Go version to 1.14 12 | 13 | FIXES: 14 | 15 | - [99](https://github.com/nginx/nginx-prometheus-exporter/pull/99): Fix link to metrics path. Thanks to [Yoan Blanc](https://github.com/greut). 16 | - [101](https://github.com/nginx/nginx-prometheus-exporter/pull/101): docs: fix dockerfile link. Thanks to [Eric Carboni](https://github.com/eric-hc). 17 | 18 | UPGRADE: 19 | 20 | - Use the 0.8.0 image from our DockerHub: `nginx/nginx-prometheus-exporter:0.8.0` 21 | - Download the latest binaries from [GitHub releases page](https://github.com/nginx/nginx-prometheus-exporter/releases/tag/v0.8.0). 22 | 23 | COMPATIBILITY: 24 | 25 | - NGINX 0.1.18 or newer. 26 | - NGINX Plus R19 or newer. 27 | 28 | ## 0.7.0 29 | 30 | FEATURES: 31 | 32 | - [86](https://github.com/nginx/nginx-prometheus-exporter/pull/86): Implemented TLS client certificate 33 | authentication. Thanks to [Fabian Lüpke](https://github.com/Fluepke). 34 | 35 | FIXES: 36 | 37 | - [96](https://github.com/nginx/nginx-prometheus-exporter/pull/96): Add const labels to upMetric. Thanks to [Robert 38 | Toth](https://github.com/robert-toth). 39 | 40 | UPGRADE: 41 | 42 | - Use the 0.7.0 image from our DockerHub: `nginx/nginx-prometheus-exporter:0.7.0` 43 | - Download the latest binaries from [GitHub releases page](https://github.com/nginx/nginx-prometheus-exporter/releases/tag/v0.7.0). 44 | 45 | COMPATIBILITY: 46 | 47 | - NGINX 0.1.18 or newer. 48 | - NGINX Plus R19 or newer. 49 | 50 | ## 0.6.0 51 | 52 | FEATURES: 53 | 54 | - [77](https://github.com/nginx/nginx-prometheus-exporter/pull/77): Add constLabels support via cli arg/env variable. 55 | 56 | CHANGES: 57 | 58 | - Update alpine image. 59 | 60 | UPGRADE: 61 | 62 | - Use the 0.6.0 image from our DockerHub: `nginx/nginx-prometheus-exporter:0.6.0` 63 | - Download the latest binaries from [GitHub releases page](https://github.com/nginx/nginx-prometheus-exporter/releases/tag/v0.6.0). 64 | 65 | COMPATIBILITY: 66 | 67 | - NGINX 0.1.18 or newer. 68 | - NGINX Plus R19 or newer. 69 | 70 | ## 0.5.0 71 | 72 | FEATURES: 73 | 74 | - [70](https://github.com/nginx/nginx-prometheus-exporter/pull/70): Set user agent on scrape requests to nginx. 75 | - [68](https://github.com/nginx/nginx-prometheus-exporter/pull/68): Add ability to scrape and listen on unix domain 76 | sockets. 77 | - [64](https://github.com/nginx/nginx-prometheus-exporter/pull/64): Add location zone and resolver metric support. 78 | 79 | FIXES: 80 | 81 | - [73](https://github.com/nginx/nginx-prometheus-exporter/pull/73): Fix typo in stream_zone_sync_status_nodes_online 82 | metric description. 83 | - [71](https://github.com/nginx/nginx-prometheus-exporter/pull/71): Do not assume default datasource in Grafana 84 | panels. 85 | - [62](https://github.com/nginx/nginx-prometheus-exporter/pull/62): Set correct nginx_up query and instance variable 86 | expression. 87 | 88 | UPGRADE: 89 | 90 | - Use the 0.5.0 image from our DockerHub: `nginx/nginx-prometheus-exporter:0.5.0` 91 | - Download the latest binaries from [GitHub releases page](https://github.com/nginx/nginx-prometheus-exporter/releases/tag/v0.5.0). 92 | 93 | COMPATIBILITY: 94 | 95 | - NGINX 0.1.18 or newer. 96 | - NGINX Plus R19 or newer. 97 | 98 | ## 0.4.2 99 | 100 | FIXES: 101 | 102 | - [60](https://github.com/nginx/nginx-prometheus-exporter/pull/60): _Fix session metrics for stream server zones_. 103 | Session metrics with a status of `4xx` or `5xx` are now correctly reported. Previously they were always reported as 104 | `0`. 105 | 106 | UPGRADE: 107 | 108 | - Use the 0.4.2 image from our DockerHub: `nginx/nginx-prometheus-exporter:0.4.2` 109 | - Download the latest binaries from [GitHub releases page](https://github.com/nginx/nginx-prometheus-exporter/releases/tag/v0.4.2). 110 | 111 | COMPATIBILITY: 112 | 113 | - NGINX 0.1.18 or newer. 114 | - NGINX Plus R18 or newer. 115 | 116 | ## 0.4.1 117 | 118 | FIXES: 119 | 120 | - [55](https://github.com/nginx/nginx-prometheus-exporter/pull/55): Do not export zone sync metrics if they are not 121 | reported by NGINX Plus. Previously, in such case, the metrics were exported with zero values. 122 | 123 | UPGRADE: 124 | 125 | - Use the 0.4.1 image from our DockerHub: `nginx/nginx-prometheus-exporter:0.4.1` 126 | - Download the latest binaries from [GitHub releases page](https://github.com/nginx/nginx-prometheus-exporter/releases/tag/v0.4.1). 127 | 128 | COMPATIBILITY: 129 | 130 | - NGINX 0.1.18 or newer. 131 | - NGINX Plus R18 or newer. 132 | 133 | ## 0.4.0 134 | 135 | FEATURES: 136 | 137 | - [50](https://github.com/nginx/nginx-prometheus-exporter/pull/50): Add zone sync metrics support. 138 | - [37](https://github.com/nginx/nginx-prometheus-exporter/pull/37): Implement a way to retry connection to NGINX if 139 | it is unreachable. Add -nginx.retries for setting the number of retries and -nginx.retry-interval for setting the 140 | interval between retries, both as cli-arguments. 141 | 142 | UPGRADE: 143 | 144 | - Use the 0.4.0 image from our DockerHub: `nginx/nginx-prometheus-exporter:0.4.0` 145 | - Download the latest binaries from [GitHub releases page](https://github.com/nginx/nginx-prometheus-exporter/releases/tag/v0.4.0). 146 | 147 | COMPATIBILITY: 148 | 149 | - NGINX 0.1.18 or newer. 150 | - NGINX Plus R18 or newer. 151 | 152 | ## 0.3.0 153 | 154 | FEATURES: 155 | 156 | - [32](https://github.com/nginx/nginx-prometheus-exporter/pull/32): Add nginxexporter_build_info metric. 157 | - [31](https://github.com/nginx/nginx-prometheus-exporter/pull/31): Implement nginx_up and nginxplus_up metrics. Add 158 | -nginx.timeout cli argument for setting a timeout for scrapping metrics from NGINX or NGINX Plus. 159 | 160 | UPGRADE: 161 | 162 | - Use the 0.3.0 image from our DockerHub: `nginx/nginx-prometheus-exporter:0.3.0` 163 | - Download the latest binaries from [GitHub releases page](https://github.com/nginx/nginx-prometheus-exporter/releases/tag/v0.3.0). 164 | 165 | COMPATIBILITY: 166 | 167 | - NGINX 0.1.18 or newer. 168 | - NGINX Plus R14 or newer. 169 | 170 | ## 0.2.0 171 | 172 | FEATURES: 173 | 174 | - [16](https://github.com/nginx/nginx-prometheus-exporter/pull/16): Add stream metrics support. 175 | - [13](https://github.com/nginx/nginx-prometheus-exporter/pull/13): Add a flag for controlling SSL verification of 176 | NGINX stub_status/API endpoint. Thanks to [Raza Jhaveri](https://github.com/razaj92). 177 | - [3](https://github.com/nginx/nginx-prometheus-exporter/pull/3): Support for environment variables. 178 | 179 | UPGRADE: 180 | 181 | - Use the 0.2.0 image from our DockerHub: `nginx/nginx-prometheus-exporter:0.2.0` 182 | - Download the latest binaries from [GitHub releases page](https://github.com/nginx/nginx-prometheus-exporter/releases/tag/v0.2.0). 183 | 184 | COMPATIBILITY: 185 | 186 | - NGINX 0.1.18 or newer. 187 | - NGINX Plus R14 or newer. 188 | 189 | ## 0.1.0 190 | 191 | - Initial release. 192 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @nginx/integrations 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | This project and everyone participating in it is governed by this code. 4 | 5 | ## Our Pledge 6 | 7 | In the interest of fostering an open and welcoming environment, we as 8 | contributors and maintainers pledge to making participation in our project and 9 | our community a harassment-free experience for everyone, regardless of age, body 10 | size, disability, ethnicity, sex characteristics, gender identity and expression, 11 | level of experience, education, socio-economic status, nationality, personal 12 | appearance, race, religion, or sexual identity and orientation. 13 | 14 | ## Our Standards 15 | 16 | Examples of behavior that contributes to creating a positive environment 17 | include: 18 | 19 | - Using welcoming and inclusive language 20 | - Being respectful of differing viewpoints and experiences 21 | - Gracefully accepting constructive criticism 22 | - Focusing on what is best for the community 23 | - Showing empathy towards other community members 24 | 25 | Examples of unacceptable behavior by participants include: 26 | 27 | - The use of sexualized language or imagery and unwelcome sexual attention or 28 | advances 29 | - Trolling, insulting/derogatory comments, and personal or political attacks 30 | - Public or private harassment 31 | - Publishing others' private information, such as a physical or electronic 32 | address, without explicit permission 33 | - Other conduct which could reasonably be considered inappropriate in a 34 | professional setting 35 | 36 | ## Our Responsibilities 37 | 38 | Project maintainers are responsible for clarifying the standards of acceptable 39 | behavior and are expected to take appropriate and fair corrective action in 40 | response to any instances of unacceptable behavior. 41 | 42 | Project maintainers have the right and responsibility to remove, edit, or 43 | reject comments, commits, code, wiki edits, issues, and other contributions 44 | that are not aligned to this Code of Conduct, or to ban temporarily or 45 | permanently any contributor for other behaviors that they deem inappropriate, 46 | threatening, offensive, or harmful. 47 | 48 | ## Scope 49 | 50 | This Code of Conduct applies both within project spaces and in public spaces 51 | when an individual is representing the project or its community. Examples of 52 | representing a project or community include using an official project e-mail 53 | address, posting via an official social media account, or acting as an appointed 54 | representative at an online or offline event. Representation of a project may be 55 | further defined and clarified by project maintainers. 56 | 57 | ## Enforcement 58 | 59 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 60 | reported by contacting the project team at . All 61 | complaints will be reviewed and investigated and will result in a response that 62 | is deemed necessary and appropriate to the circumstances. The project team is 63 | obligated to maintain confidentiality with regard to the reporter of an incident. 64 | Further details of specific enforcement policies may be posted separately. 65 | 66 | Project maintainers who do not follow or enforce the Code of Conduct in good 67 | faith may face temporary or permanent repercussions as determined by other 68 | members of the project's leadership. 69 | 70 | ## Attribution 71 | 72 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 73 | available at 74 | 75 | [homepage]: https://www.contributor-covenant.org 76 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | The following is a set of guidelines for contributing to the NGINX Prometheus Exporter. We really appreciate that you 4 | are considering contributing! 5 | 6 | ## Table Of Contents 7 | 8 | 9 | 10 | ## Table of Contents 11 | 12 | - [Ask a Question](#ask-a-question) 13 | - [Getting Started](#getting-started) 14 | - [Project Structure](#project-structure) 15 | - [Contributing](#contributing) 16 | - [Report a Bug](#report-a-bug) 17 | - [Suggest an Enhancement](#suggest-an-enhancement) 18 | - [Open a Pull Request](#open-a-pull-request) 19 | - [Issue lifecycle](#issue-lifecycle) 20 | - [F5 Contributor License Agreement (CLA)](#f5-contributor-license-agreement-cla) 21 | - [Style Guides](#style-guides) 22 | - [Git Style Guide](#git-style-guide) 23 | - [Go Style Guide](#go-style-guide) 24 | 25 | 26 | 27 | ## Ask a Question 28 | 29 | To ask a question, please use [GitHub Discussions](https://github.com/nginx/nginx-prometheus-exporter/discussions). 30 | 31 | Please reserve GitHub issues for feature requests and bugs rather than general questions. 32 | 33 | ## Getting Started 34 | 35 | Follow our [Getting Started Guide](README.md#getting-started) to get the NGINX Prometheus Exporter up and running. 36 | 37 | ### Project Structure 38 | 39 | - This Prometheus Exporter is written in Go and supports both the open source NGINX software and NGINX Plus. 40 | - We use [Go modules](https://github.com/golang/go/wiki/Modules) for managing dependencies. 41 | 42 | ## Contributing 43 | 44 | ### Report a Bug 45 | 46 | To report a bug, open an issue on GitHub with the label `bug` using the available bug report issue template. Please 47 | ensure the issue has not already been reported. 48 | 49 | ### Suggest an Enhancement 50 | 51 | To suggest an enhancement, please create an issue on GitHub with the label `enhancement` using the available feature 52 | issue template. 53 | 54 | ### Open a Pull Request 55 | 56 | - Fork the repo, create a branch, submit a PR when your changes are tested and ready for review 57 | - Fill in [our pull request template](.github/PULL_REQUEST_TEMPLATE.md) 58 | 59 | > **Note** 60 | > 61 | > If you’d like to implement a new feature, please consider creating a feature request issue first to start a discussion 62 | > about the feature. 63 | 64 | ### Issue lifecycle 65 | 66 | - When an issue or PR is created, it will be triaged by the core development team and assigned a label to indicate the 67 | type of issue it is (bug, feature request, etc) and to determine the milestone. Please see the [Issue 68 | Lifecycle](ISSUE_LIFECYCLE.md) document for more information. 69 | 70 | ### F5 Contributor License Agreement (CLA) 71 | 72 | F5 requires all external contributors to agree to the terms of the F5 CLA (available [here](https://github.com/f5/.github/blob/main/CLA/cla-markdown.md)) 73 | before any of their changes can be incorporated into an F5 Open Source repository. 74 | 75 | If you have not yet agreed to the F5 CLA terms and submit a PR to this repository, a bot will prompt you to view and 76 | agree to the F5 CLA. You will have to agree to the F5 CLA terms through a comment in the PR before any of your changes 77 | can be merged. Your agreement signature will be safely stored by F5 and no longer be required in future PRs. 78 | 79 | ## Style Guides 80 | 81 | ### Git Style Guide 82 | 83 | - Keep a clean, concise and meaningful git commit history on your branch, rebasing locally and squashing before 84 | submitting a PR 85 | - Follow the guidelines of writing a good commit message as described here 86 | and summarized in the next few points 87 | - In the subject line, use the present tense ("Add feature" not "Added feature") 88 | - In the subject line, use the imperative mood ("Move cursor to..." not "Moves cursor to...") 89 | - Limit the subject line to 72 characters or less 90 | - Reference issues and pull requests liberally after the subject line 91 | - Add more detailed description in the body of the git message (`git commit -a` to give you more space and time in 92 | your text editor to write a good message instead of `git commit -am`) 93 | 94 | ### Go Style Guide 95 | 96 | - Run `gofmt` over your code to automatically resolve a lot of style issues. Most editors support this running 97 | automatically when saving a code file. 98 | - Run `go lint` and `go vet` on your code too to catch any other issues. 99 | - Follow this guide on some good practice and idioms for Go - 100 | - To check for extra issues, install [golangci-lint](https://github.com/golangci/golangci-lint) and run `make lint` or 101 | `golangci-lint run` 102 | -------------------------------------------------------------------------------- /ISSUE_LIFECYCLE.md: -------------------------------------------------------------------------------- 1 | # Issue Lifecycle 2 | 3 | To ensure a balance between work carried out by the NGINX engineering team while encouraging community involvement on 4 | this project, we use the following issue lifecycle. (Note: The issue *creator* refers to the community member that 5 | created the issue. The issue *owner* refers to the NGINX team member that is responsible for managing the issue 6 | lifecycle.) 7 | 8 | 1. New issue created by community member. 9 | 10 | 2. Assign issue owner: All new issues are assigned an owner on the NGINX engineering team. This owner shepherds the 11 | issue through the subsequent stages in the issue lifecycle. 12 | 13 | 3. Determine issue type: This is done with automation where possible, and manually by the owner where necessary. The 14 | associated label is applied to the issue. 15 | 16 | Possible Issue Types: 17 | 18 | - `needs more info`: The owner should use the issue to request information from the creator. If we don't receive the 19 | needed information within 7 days, automation closes the issue. 20 | 21 | - `bug`: The implementation of a feature is not correct. 22 | 23 | - `proposal`: Request for a change. This can be a new feature, tackling technical debt, documentation changes, or 24 | improving existing features. 25 | 26 | - `question`: The owner converts the issue to a github discussion and engages the creator. 27 | 28 | 4. Determine milestone: The owner, in collaboration with the wider team (PM & engineering), determines what milestone to 29 | attach to an issue. Generally, milestones correspond to product releases - however there are two 'magic' milestones 30 | with special meanings (not tied to a specific release): 31 | 32 | - Issues assigned to backlog: Our team is in favour of implementing the feature request/fixing the issue, however the 33 | implementation is not yet assigned to a concrete release. If and when a `backlog` issue aligns well with our 34 | roadmap, it will be scheduled for a concrete iteration. We review and update our roadmap at least once every 35 | quarter. The `backlog` list helps us shape our roadmap, but it is not the only source of input. Therefore, some 36 | `backlog` items may eventually be closed as `out of scope`, or relabelled as `backlog candidate` once it becomes 37 | clear that they do not align with our evolving roadmap. 38 | 39 | - Issues assigned to `backlog candidate`: Our team does not intend to implement the feature/fix request described in 40 | the issue and wants the community to weigh in before we make our final decision. 41 | 42 | `backlog` issues can be labeled by the owner as `help wanted` and/or `good first issue` as appropriate. 43 | 44 | 5. Promotion of `backlog candidate` issue to `backlog` issue: If an issue labelled `backlog candidate` receives more 45 | than 30 upvotes within 60 days, we promote the issue by applying the `backlog` label. While issues promoted in this 46 | manner have not been committed to a particular release, we welcome PRs from the community on them. 47 | 48 | If an issue does not make our roadmap and has not been moved to a discussion, it is closed with the label `out of 49 | scope`. The goal is to get every issue in the issues list to one of the following end states: 50 | 51 | - An assigned release. 52 | - The `backlog` label. 53 | - Closed as `out of scope`. 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018 Nginx, Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION = 1.4.2 2 | TAG = $(VERSION) 3 | PREFIX = nginx/nginx-prometheus-exporter 4 | # renovate: datasource=github-tags depName=golangci/golangci-lint 5 | GOLANGCI_LINT_VERSION = v2.1.6 6 | 7 | .DEFAULT_GOAL:=nginx-prometheus-exporter 8 | 9 | .PHONY: help 10 | help: Makefile ## Display this help 11 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "; printf "Usage:\n\n make \033[36m\033[0m [VARIABLE=value...]\n\nTargets:\n\n"}; {printf " \033[36m%-30s\033[0m %s\n", $$1, $$2}' 12 | 13 | .PHONY: nginx-prometheus-exporter 14 | nginx-prometheus-exporter: ## Build nginx-prometheus-exporter binary 15 | CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X github.com/prometheus/common/version.Version=$(VERSION)" -o nginx-prometheus-exporter 16 | 17 | .PHONY: build-goreleaser 18 | build-goreleaser: ## Build all binaries using GoReleaser 19 | @goreleaser -v || (code=$$?; printf "\033[0;31mError\033[0m: there was a problem with GoReleaser. Follow the docs to install it https://goreleaser.com/install\n"; exit $$code) 20 | goreleaser build --clean --snapshot 21 | 22 | .PHONY: lint 23 | lint: ## Run linter 24 | go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) run --fix 25 | 26 | .PHONY: test 27 | test: ## Run tests 28 | go test ./... -race -shuffle=on -coverprofile=coverage.txt -covermode=atomic 29 | 30 | .PHONY: container 31 | container: ## Build container image 32 | docker build --build-arg VERSION=$(VERSION) --target container -f build/Dockerfile -t $(PREFIX):$(TAG) . 33 | 34 | .PHONY: push 35 | push: container ## Push container image 36 | docker push $(PREFIX):$(TAG) 37 | 38 | .PHONY: deps 39 | deps: ## Add missing and remove unused modules, verify deps and download them to local cache 40 | @go mod tidy && go mod verify && go mod download 41 | 42 | .PHONY: clean 43 | clean: ## Clean up 44 | -rm -r dist 45 | -rm nginx-prometheus-exporter 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![OpenSSFScorecard](https://api.securityscorecards.dev/projects/github.com/nginx/nginx-prometheus-exporter/badge)](https://scorecard.dev/viewer/?uri=github.com/nginx/nginx-prometheus-exporter) 3 | [![CI](https://github.com/nginx/nginx-prometheus-exporter/actions/workflows/ci.yml/badge.svg)](https://github.com/nginx/nginx-prometheus-exporter/actions/workflows/ci.yml) 4 | [![FOSSA Status](https://app.fossa.com/api/projects/custom%2B5618%2Fgithub.com%2Fnginx%2Fnginx-prometheus-exporter.svg?type=shield)](https://app.fossa.com/projects/custom%2B5618%2Fgithub.com%2Fnginx%2Fnginx-prometheus-exporter?ref=badge_shield) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/nginx/nginx-prometheus-exporter)](https://goreportcard.com/report/github.com/nginx/nginx-prometheus-exporter) 6 | [![codecov](https://codecov.io/gh/nginx/nginx-prometheus-exporter/graph/badge.svg?token=J6Oz10LWy3)](https://codecov.io/gh/nginx/nginx-prometheus-exporter) 7 | ![GitHub all releases](https://img.shields.io/github/downloads/nginx/nginx-prometheus-exporter/total?logo=github) 8 | ![GitHub release (latest by SemVer)](https://img.shields.io/github/downloads/nginx/nginx-prometheus-exporter/latest/total?sort=semver&logo=github) 9 | [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/nginx/nginx-prometheus-exporter?logo=github&sort=semver)](https://github.com/nginx/nginx-prometheus-exporter/releases/latest) 10 | [![nginx-prometheus-exporter](https://snapcraft.io/nginx-prometheus-exporter/badge.svg)](https://snapcraft.io/nginx-prometheus-exporter) 11 | ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/nginx/nginx-prometheus-exporter?logo=go) 12 | [![Docker Pulls](https://img.shields.io/docker/pulls/nginx/nginx-prometheus-exporter?logo=docker&logoColor=white)](https://hub.docker.com/r/nginx/nginx-prometheus-exporter) 13 | ![Docker Image Size (latest semver)](https://img.shields.io/docker/image-size/nginx/nginx-prometheus-exporter?logo=docker&logoColor=white&sort=semver) 14 | [![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) 15 | 16 | # NGINX Prometheus Exporter 17 | 18 | NGINX Prometheus exporter makes it possible to monitor NGINX or NGINX Plus using Prometheus. 19 | 20 | 21 | 22 | ## Table of Contents 23 | 24 | - [Overview](#overview) 25 | - [Getting Started](#getting-started) 26 | - [A Note about NGINX Ingress Controller](#a-note-about-nginx-ingress-controller) 27 | - [Prerequisites](#prerequisites) 28 | - [Running the Exporter in a Docker Container](#running-the-exporter-in-a-docker-container) 29 | - [Running the Exporter Binary](#running-the-exporter-binary) 30 | - [Usage](#usage) 31 | - [Command-line Arguments](#command-line-arguments) 32 | - [Exported Metrics](#exported-metrics) 33 | - [Common metrics](#common-metrics) 34 | - [Metrics for NGINX OSS](#metrics-for-nginx-oss) 35 | - [Stub status metrics](#stub-status-metrics) 36 | - [Metrics for NGINX Plus](#metrics-for-nginx-plus) 37 | - [Connections](#connections) 38 | - [HTTP](#http) 39 | - [SSL](#ssl) 40 | - [HTTP Server Zones](#http-server-zones) 41 | - [Stream Server Zones](#stream-server-zones) 42 | - [HTTP Upstreams](#http-upstreams) 43 | - [Stream Upstreams](#stream-upstreams) 44 | - [Stream Zone Sync](#stream-zone-sync) 45 | - [Location Zones](#location-zones) 46 | - [Resolver](#resolver) 47 | - [HTTP Requests Rate Limiting](#http-requests-rate-limiting) 48 | - [HTTP Connections Limiting](#http-connections-limiting) 49 | - [Stream Connections Limiting](#stream-connections-limiting) 50 | - [Cache](#cache) 51 | - [Worker](#worker) 52 | - [Troubleshooting](#troubleshooting) 53 | - [Releases](#releases) 54 | - [Docker images](#docker-images) 55 | - [Binaries](#binaries) 56 | - [Homebrew](#homebrew) 57 | - [Snap](#snap) 58 | - [Scoop](#scoop) 59 | - [Nix](#nix) 60 | - [Building the Exporter](#building-the-exporter) 61 | - [Building the Docker Image](#building-the-docker-image) 62 | - [Building the Binary](#building-the-binary) 63 | - [Grafana Dashboard](#grafana-dashboard) 64 | - [SBOM (Software Bill of Materials)](#sbom-software-bill-of-materials) 65 | - [Binaries](#binaries-1) 66 | - [Docker Image](#docker-image) 67 | - [Provenance](#provenance) 68 | - [Contacts](#contacts) 69 | - [Contributing](#contributing) 70 | - [Support](#support) 71 | - [License](#license) 72 | 73 | 74 | 75 | ## Overview 76 | 77 | [NGINX](https://nginx.org) exposes a handful of metrics via the [stub_status 78 | page](https://nginx.org/en/docs/http/ngx_http_stub_status_module.html#stub_status). [NGINX 79 | Plus](https://www.nginx.com/products/nginx/) provides a richer set of metrics via the 80 | [API](https://nginx.org/en/docs/http/ngx_http_api_module.html) and the [monitoring 81 | dashboard](https://docs.nginx.com/nginx/admin-guide/monitoring/live-activity-monitoring/). NGINX Prometheus exporter 82 | fetches the metrics from a single NGINX or NGINX Plus, converts the metrics into appropriate Prometheus metrics types 83 | and finally exposes them via an HTTP server to be collected by [Prometheus](https://prometheus.io/). 84 | 85 | ## Getting Started 86 | 87 | In this section, we show how to quickly run NGINX Prometheus Exporter for NGINX or NGINX Plus. 88 | 89 | ### A Note about NGINX Ingress Controller 90 | 91 | If you’d like to use the NGINX Prometheus Exporter with [NGINX Ingress 92 | Controller](https://github.com/nginx/kubernetes-ingress/) for Kubernetes, see [this 93 | doc](https://docs.nginx.com/nginx-ingress-controller/logging-and-monitoring/prometheus/) for the installation 94 | instructions. 95 | 96 | ### Prerequisites 97 | 98 | We assume that you have already installed Prometheus and NGINX or NGINX Plus. Additionally, you need to: 99 | 100 | - Expose the built-in metrics in NGINX/NGINX Plus: 101 | - For NGINX, expose the [stub_status 102 | page](https://nginx.org/en/docs/http/ngx_http_stub_status_module.html#stub_status) at `/stub_status` on port `8080`. 103 | - For NGINX Plus, expose the [API](https://nginx.org/en/docs/http/ngx_http_api_module.html#api) at `/api` on port 104 | `8080`. 105 | - Configure Prometheus to scrape metrics from the server with the exporter. Note that the default scrape port of the 106 | exporter is `9113` and the default metrics path -- `/metrics`. 107 | 108 | ### Running the Exporter in a Docker Container 109 | 110 | To start the exporter we use the [docker run](https://docs.docker.com/engine/reference/run/) command. 111 | 112 | - To export NGINX metrics, run: 113 | 114 | ```console 115 | docker run -p 9113:9113 nginx/nginx-prometheus-exporter:1.4.2 --nginx.scrape-uri=http://:8080/stub_status 116 | ``` 117 | 118 | where `` is the IP address/DNS name, through which NGINX is available. 119 | 120 | - To export NGINX Plus metrics, run: 121 | 122 | ```console 123 | docker run -p 9113:9113 nginx/nginx-prometheus-exporter:1.4.2 --nginx.plus --nginx.scrape-uri=http://:8080/api 124 | ``` 125 | 126 | where `` is the IP address/DNS name, through which NGINX Plus is available. 127 | 128 | ### Running the Exporter Binary 129 | 130 | - To export NGINX metrics, run: 131 | 132 | ```console 133 | nginx-prometheus-exporter --nginx.scrape-uri=http://:8080/stub_status 134 | ``` 135 | 136 | where `` is the IP address/DNS name, through which NGINX is available. 137 | 138 | - To export NGINX Plus metrics: 139 | 140 | ```console 141 | nginx-prometheus-exporter --nginx.plus --nginx.scrape-uri=http://:8080/api 142 | ``` 143 | 144 | where `` is the IP address/DNS name, through which NGINX Plus is available. 145 | 146 | - To scrape NGINX metrics with unix domain sockets, run: 147 | 148 | ```console 149 | nginx-prometheus-exporter --nginx.scrape-uri=unix::/stub_status 150 | ``` 151 | 152 | where `` is the path to unix domain socket, through which NGINX stub status is available. 153 | 154 | **Note**. The `nginx-prometheus-exporter` is not a daemon. To run the exporter as a system service (daemon), you can 155 | follow the example in [examples/systemd](./examples/systemd/README.md). Alternatively, you can run the exporter 156 | in a Docker container. 157 | 158 | ## Usage 159 | 160 | ### Command-line Arguments 161 | 162 | ```console 163 | usage: nginx-prometheus-exporter [] 164 | 165 | 166 | Flags: 167 | -h, --[no-]help Show context-sensitive help (also try --help-long and --help-man). 168 | --[no-]web.systemd-socket Use systemd socket activation listeners instead 169 | of port listeners (Linux only). ($SYSTEMD_SOCKET) 170 | --web.listen-address=:9113 ... 171 | Addresses on which to expose metrics and web interface. Repeatable for multiple addresses. ($LISTEN_ADDRESS) 172 | --web.config.file="" Path to configuration file that can enable TLS or authentication. See: https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md ($CONFIG_FILE) 173 | --web.telemetry-path="/metrics" 174 | Path under which to expose metrics. ($TELEMETRY_PATH) 175 | --[no-]nginx.plus Start the exporter for NGINX Plus. By default, the exporter is started for NGINX. ($NGINX_PLUS) 176 | --nginx.scrape-uri=http://127.0.0.1:8080/stub_status ... 177 | A URI or unix domain socket path for scraping NGINX or NGINX Plus metrics. For NGINX, the stub_status page must be available through the URI. For NGINX Plus -- the API. Repeatable for multiple URIs. ($SCRAPE_URI) 178 | --[no-]nginx.ssl-verify Perform SSL certificate verification. ($SSL_VERIFY) 179 | --nginx.ssl-ca-cert="" Path to the PEM encoded CA certificate file used to validate the servers SSL certificate. ($SSL_CA_CERT) 180 | --nginx.ssl-client-cert="" 181 | Path to the PEM encoded client certificate file to use when connecting to the server. ($SSL_CLIENT_CERT) 182 | --nginx.ssl-client-key="" Path to the PEM encoded client certificate key file to use when connecting to the server. ($SSL_CLIENT_KEY) 183 | --nginx.timeout=5s A timeout for scraping metrics from NGINX or NGINX Plus. ($TIMEOUT) 184 | --prometheus.const-label=PROMETHEUS.CONST-LABEL ... 185 | Label that will be used in every metric. Format is label=value. It can be repeated multiple times. ($CONST_LABELS) 186 | --log.level=info Only log messages with the given severity or above. One of: [debug, info, warn, error] 187 | --log.format=logfmt Output format of log messages. One of: [logfmt, json] 188 | --[no-]version Show application version. 189 | ``` 190 | 191 | ## Exported Metrics 192 | 193 | ### Common metrics 194 | 195 | | Name | Type | Description | Labels | 196 | | -------------------------------------------- | -------- | -------------------------------------------- | ------------------------------------------------------------------------- | 197 | | `nginx_exporter_build_info` | Gauge | Shows the exporter build information. | `branch`, `goarch`, `goos`, `goversion`, `revision`, `tags` and `version` | 198 | | `promhttp_metric_handler_requests_total` | Counter | Total number of scrapes by HTTP status code. | `code` (the HTTP status code) | 199 | | `promhttp_metric_handler_requests_in_flight` | Gauge | Current number of scrapes being served. | [] | 200 | | `go_*` | Multiple | Go runtime metrics. | [] | 201 | 202 | ### Metrics for NGINX OSS 203 | 204 | | Name | Type | Description | Labels | 205 | | ---------- | ----- | ------------------------------------------------------------------------------------------------ | ------ | 206 | | `nginx_up` | Gauge | Shows the status of the last metric scrape: `1` for a successful scrape and `0` for a failed one | [] | 207 | 208 | #### [Stub status metrics](https://nginx.org/en/docs/http/ngx_http_stub_status_module.html) 209 | 210 | | Name | Type | Description | Labels | 211 | | ---------------------------- | ------- | ------------------------------------------------------------------- | ------ | 212 | | `nginx_connections_accepted` | Counter | Accepted client connections. | [] | 213 | | `nginx_connections_active` | Gauge | Active client connections. | [] | 214 | | `nginx_connections_handled` | Counter | Handled client connections. | [] | 215 | | `nginx_connections_reading` | Gauge | Connections where NGINX is reading the request header. | [] | 216 | | `nginx_connections_waiting` | Gauge | Idle client connections. | [] | 217 | | `nginx_connections_writing` | Gauge | Connections where NGINX is writing the response back to the client. | [] | 218 | | `nginx_http_requests_total` | Counter | Total http requests. | [] | 219 | 220 | ### Metrics for NGINX Plus 221 | 222 | | Name | Type | Description | Labels | 223 | | -------------- | ----- | ------------------------------------------------------------------------------------------------ | ------ | 224 | | `nginxplus_up` | Gauge | Shows the status of the last metric scrape: `1` for a successful scrape and `0` for a failed one | [] | 225 | 226 | #### [Connections](https://nginx.org/en/docs/http/ngx_http_api_module.html#def_nginx_connections) 227 | 228 | | Name | Type | Description | Labels | 229 | | -------------------------------- | ------- | ---------------------------------- | ------ | 230 | | `nginxplus_connections_accepted` | Counter | Accepted client connections | [] | 231 | | `nginxplus_connections_active` | Gauge | Active client connections | [] | 232 | | `nginxplus_connections_dropped` | Counter | Dropped client connections dropped | [] | 233 | | `nginxplus_connections_idle` | Gauge | Idle client connections | [] | 234 | 235 | #### [HTTP](https://nginx.org/en/docs/http/ngx_http_api_module.html#http_) 236 | 237 | | Name | Type | Description | Labels | 238 | | --------------------------------- | ------- | --------------------- | ------ | 239 | | `nginxplus_http_requests_total` | Counter | Total http requests | [] | 240 | | `nginxplus_http_requests_current` | Gauge | Current http requests | [] | 241 | 242 | #### [SSL](https://nginx.org/en/docs/http/ngx_http_api_module.html#def_nginx_ssl_object) 243 | 244 | | Name | Type | Description | Labels | 245 | | --------------------------------- | ------- | ----------------------------------- | ------ | 246 | | `nginxplus_ssl_handshakes` | Counter | Successful SSL handshakes | [] | 247 | | `nginxplus_ssl_handshakes_failed` | Counter | Failed SSL handshakes | [] | 248 | | `nginxplus_ssl_session_reuses` | Counter | Session reuses during SSL handshake | [] | 249 | 250 | #### [HTTP Server Zones](https://nginx.org/en/docs/http/ngx_http_api_module.html#def_nginx_http_server_zone) 251 | 252 | | Name | Type | Description | Labels | 253 | | ---------------------------------------- | ------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | 254 | | `nginxplus_server_zone_processing` | Gauge | Client requests that are currently being processed | `server_zone` | 255 | | `nginxplus_server_zone_requests` | Counter | Total client requests | `server_zone` | 256 | | `nginxplus_server_zone_responses` | Counter | Total responses sent to clients | `code` (the response status code. The values are: `1xx`, `2xx`, `3xx`, `4xx` and `5xx`), `server_zone` | 257 | | `nginxplus_server_zone_responses_codes` | Counter | Total responses sent to clients by code | `code` (the response status code. The possible values are [here](https://www.nginx.com/resources/wiki/extending/api/http/)), `server_zone` | 258 | | `nginxplus_server_zone_discarded` | Counter | Requests completed without sending a response | `server_zone` | 259 | | `nginxplus_server_zone_received` | Counter | Bytes received from clients | `server_zone` | 260 | | `nginxplus_server_zone_sent` | Counter | Bytes sent to clients | `server_zone` | 261 | | `nginxplus_server_ssl_handshakes` | Counter | Successful SSL handshakes | `server_zone` | 262 | | `nginxplus_server_ssl_handshakes_failed` | Counter | Failed SSL handshakes | `server_zone` | 263 | | `nginxplus_server_ssl_session_reuses` | Counter | Session reuses during SSL handshake | `server_zone` | 264 | 265 | #### [Stream Server Zones](https://nginx.org/en/docs/http/ngx_http_api_module.html#def_nginx_stream_server_zone) 266 | 267 | | Name | Type | Description | Labels | 268 | | ----------------------------------------------- | ------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------- | 269 | | `nginxplus_stream_server_zone_processing` | Gauge | Client connections that are currently being processed | `server_zone` | 270 | | `nginxplus_stream_server_zone_connections` | Counter | Total connections | `server_zone` | 271 | | `nginxplus_stream_server_zone_sessions` | Counter | Total sessions completed | `code` (the response status code. The values are: `2xx`, `4xx`, and `5xx`), `server_zone` | 272 | | `nginxplus_stream_server_zone_discarded` | Counter | Connections completed without creating a session | `server_zone` | 273 | | `nginxplus_stream_server_zone_received` | Counter | Bytes received from clients | `server_zone` | 274 | | `nginxplus_stream_server_zone_sent` | Counter | Bytes sent to clients | `server_zone` | 275 | | `nginxplus_stream_server_ssl_handshakes` | Counter | Successful SSL handshakes | `server_zone` | 276 | | `nginxplus_stream_server_ssl_handshakes_failed` | Counter | Failed SSL handshakes | `server_zone` | 277 | | `nginxplus_stream_server_ssl_session_reuses` | Counter | Session reuses during SSL handshake | `server_zone` | 278 | 279 | #### [HTTP Upstreams](https://nginx.org/en/docs/http/ngx_http_api_module.html#def_nginx_http_upstream) 280 | 281 | > Note: for the `state` metric, the string values are converted to float64 using the following rule: `"up"` -> `1.0`, 282 | > `"draining"` -> `2.0`, `"down"` -> `3.0`, `"unavail"` –> `4.0`, `"checking"` –> `5.0`, `"unhealthy"` -> `6.0`. 283 | 284 | | Name | Type | Description | Labels | 285 | | --------------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | 286 | | `nginxplus_upstream_server_state` | Gauge | Current state | `server`, `upstream` | 287 | | `nginxplus_upstream_server_active` | Gauge | Active connections | `server`, `upstream` | 288 | | `nginxplus_upstream_server_limit` | Gauge | Limit for connections which corresponds to the max_conns parameter of the upstream server. Zero value means there is no limit | `server`, `upstream` | 289 | | `nginxplus_upstream_server_requests` | Counter | Total client requests | `server`, `upstream` | 290 | | `nginxplus_upstream_server_responses` | Counter | Total responses sent to clients | `code` (the response status code. The values are: `1xx`, `2xx`, `3xx`, `4xx` and `5xx`), `server`, `upstream` | 291 | | `nginxplus_upstream_server_responses_codes` | Counter | Total responses sent to clients by code | `code` (the response status code. The possible values are [here](https://www.nginx.com/resources/wiki/extending/api/http/)), `server`, `upstream` | 292 | | nginxplus_upstream_server_sent` | Counter | Bytes sent to this server | `server`, `upstream` | 293 | | `nginxplus_upstream_server_received` | Counter | Bytes received to this server | `server`, `upstream` | 294 | | `nginxplus_upstream_server_fails` | Counter | Number of unsuccessful attempts to communicate with the server | `server`, `upstream` | 295 | | `nginxplus_upstream_server_unavail` | Counter | How many times the server became unavailable for client requests (state 'unavail') due to the number of unsuccessful attempts reaching the max_fails threshold | `server`, `upstream` | 296 | | `nginxplus_upstream_server_header_time` | Gauge | Average time to get the response header from the server | `server`, `upstream` | 297 | | `nginxplus_upstream_server_response_time` | Gauge | Average time to get the full response from the server | `server`, `upstream` | 298 | | `nginxplus_upstream_server_health_checks_checks` | Counter | Total health check requests | `server`, `upstream` | 299 | | `nginxplus_upstream_server_health_checks_fails` | Counter | Failed health checks | `server`, `upstream` | 300 | | `nginxplus_upstream_server_health_checks_unhealthy` | Counter | How many times the server became unhealthy (state 'unhealthy') | `server`, `upstream` | 301 | | `nginxplus_upstream_server_ssl_handshakes` | Counter | Successful SSL handshakes | `server`, `upstream` | 302 | | `nginxplus_upstream_server_ssl_handshakes_failed` | Counter | Failed SSL handshakes | `server`, `upstream` | 303 | | `nginxplus_upstream_server_ssl_session_reuses` | Counter | Session reuses during SSL handshake | `server`, `upstream` | 304 | | `nginxplus_upstream_keepalive` | Gauge | Idle keepalive connections | `upstream` | 305 | | `nginxplus_upstream_zombies` | Gauge | Servers removed from the group but still processing active client requests | `upstream` | 306 | 307 | #### [Stream Upstreams](https://nginx.org/en/docs/http/ngx_http_api_module.html#def_nginx_stream_upstream) 308 | 309 | > Note: for the `state` metric, the string values are converted to float64 using the following rule: `"up"` -> `1.0`, 310 | > `"down"` -> `3.0`, `"unavail"` –> `4.0`, `"checking"` –> `5.0`, `"unhealthy"` -> `6.0`. 311 | 312 | | Name | Type | Description | Labels | 313 | | ---------------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | 314 | | `nginxplus_stream_upstream_server_state` | Gauge | Current state | `server`, `upstream` | 315 | | `nginxplus_stream_upstream_server_active` | Gauge | Active connections | `server` , `upstream` | 316 | | `nginxplus_stream_upstream_server_limit` | Gauge | Limit for connections which corresponds to the max_conns parameter of the upstream server. Zero value means there is no limit | `server` , `upstream` | 317 | | `nginxplus_stream_upstream_server_connections` | Counter | Total number of client connections forwarded to this server | `server`, `upstream` | 318 | | `nginxplus_stream_upstream_server_connect_time` | Gauge | Average time to connect to the upstream server | `server`, `upstream` | 319 | | `nginxplus_stream_upstream_server_first_byte_time` | Gauge | Average time to receive the first byte of data | `server`, `upstream` | 320 | | `nginxplus_stream_upstream_server_response_time` | Gauge | Average time to receive the last byte of data | `server`, `upstream` | 321 | | `nginxplus_stream_upstream_server_sent` | Counter | Bytes sent to this server | `server`, `upstream` | 322 | | `nginxplus_stream_upstream_server_received` | Counter | Bytes received from this server | `server`, `upstream` | 323 | | `nginxplus_stream_upstream_server_fails` | Counter | Number of unsuccessful attempts to communicate with the server | `server`, `upstream` | 324 | | `nginxplus_stream_upstream_server_unavail` | Counter | How many times the server became unavailable for client connections (state 'unavail') due to the number of unsuccessful attempts reaching the max_fails threshold | `server`, `upstream` | 325 | | `nginxplus_stream_upstream_server_health_checks_checks` | Counter | Total health check requests | `server`, `upstream` | 326 | | `nginxplus_stream_upstream_server_health_checks_fails` | Counter | Failed health checks | `server`, `upstream` | 327 | | `nginxplus_stream_upstream_server_health_checks_unhealthy` | Counter | How many times the server became unhealthy (state 'unhealthy') | `server`, `upstream` | 328 | | `nginxplus_stream_upstream_server_ssl_handshakes` | Counter | Successful SSL handshakes | `server`, `upstream` | 329 | | `nginxplus_stream_upstream_server_ssl_handshakes_failed` | Counter | Failed SSL handshakes | `server`, `upstream` | 330 | | `nginxplus_stream_upstream_server_ssl_session_reuses` | Counter | Session reuses during SSL handshake | `server`, `upstream` | 331 | | `nginxplus_stream_upstream_zombies` | Gauge | Servers removed from the group but still processing active client connections | `upstream` | 332 | 333 | #### [Stream Zone Sync](https://nginx.org/en/docs/http/ngx_http_api_module.html#def_nginx_stream_zone_sync) 334 | 335 | | Name | Type | Description | Labels | 336 | | ------------------------------------------------- | ------- | ------------------------------------------------------------ | ------ | 337 | | `nginxplus_stream_zone_sync_zone_records_pending` | Gauge | The number of records that need to be sent to the cluster | `zone` | 338 | | `nginxplus_stream_zone_sync_zone_records_total` | Gauge | The total number of records stored in the shared memory zone | `zone` | 339 | | `nginxplus_stream_zone_sync_zone_bytes_in` | Counter | Bytes received by this node | [] | 340 | | `nginxplus_stream_zone_sync_zone_bytes_out` | Counter | Bytes sent by this node | [] | 341 | | `nginxplus_stream_zone_sync_zone_msgs_in` | Counter | Total messages received by this node | [] | 342 | | `nginxplus_stream_zone_sync_zone_msgs_out` | Counter | Total messages sent by this node | [] | 343 | | `nginxplus_stream_zone_sync_zone_nodes_online` | Gauge | Number of peers this node is connected to | [] | 344 | 345 | #### [Location Zones](https://nginx.org/en/docs/http/ngx_http_api_module.html#def_nginx_http_location_zone) 346 | 347 | | Name | Type | Description | Labels | 348 | | ----------------------------------------- | ------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | 349 | | `nginxplus_location_zone_requests` | Counter | Total client requests | `location_zone` | 350 | | `nginxplus_location_zone_responses` | Counter | Total responses sent to clients | `code` (the response status code. The values are: `1xx`, `2xx`, `3xx`, `4xx` and `5xx`), `location_zone` | 351 | | `nginxplus_location_zone_responses_codes` | Counter | Total responses sent to clients by code | `code` (the response status code. The possible values are [here](https://www.nginx.com/resources/wiki/extending/api/http/)), `location_zone` | 352 | | `nginxplus_location_zone_discarded` | Counter | Requests completed without sending a response | `location_zone` | 353 | | `nginxplus_location_zone_received` | Counter | Bytes received from clients | `location_zone` | 354 | | `nginxplus_location_zone_sent` | Counter | Bytes sent to clients | `location_zone` | 355 | 356 | #### [Resolver](https://nginx.org/en/docs/http/ngx_http_api_module.html#def_nginx_resolver_zone) 357 | 358 | | Name | Type | Description | Labels | 359 | | ----------------------------- | ------- | ---------------------------------------------- | ---------- | 360 | | `nginxplus_resolver_name` | Counter | Total requests to resolve names to addresses | `resolver` | 361 | | `nginxplus_resolver_srv` | Counter | Total requests to resolve SRV records | `resolver` | 362 | | `nginxplus_resolver_addr` | Counter | Total requests to resolve addresses to names | `resolver` | 363 | | `nginxplus_resolver_noerror` | Counter | Total number of successful responses | `resolver` | 364 | | `nginxplus_resolver_formerr` | Counter | Total number of FORMERR responses | `resolver` | 365 | | `nginxplus_resolver_servfail` | Counter | Total number of SERVFAIL responses | `resolver` | 366 | | `nginxplus_resolver_nxdomain` | Counter | Total number of NXDOMAIN responses | `resolver` | 367 | | `nginxplus_resolver_notimp` | Counter | Total number of NOTIMP responses | `resolver` | 368 | | `nginxplus_resolver_refused` | Counter | Total number of REFUSED responses | `resolver` | 369 | | `nginxplus_resolver_timedout` | Counter | Total number of timed out request | `resolver` | 370 | | `nginxplus_resolver_unknown` | Counter | Total requests completed with an unknown error | `resolver` | 371 | 372 | #### [HTTP Requests Rate Limiting](https://nginx.org/en/docs/http/ngx_http_api_module.html#def_nginx_http_limit_req_zone) 373 | 374 | | Name | Type | Description | Labels | 375 | | ------------------------------------------ | ------- | --------------------------------------------------------------------------- | ------ | 376 | | `nginxplus_limit_request_passed` | Counter | Total number of requests that were neither limited nor accounted as limited | `zone` | 377 | | `nginxplus_limit_request_rejected` | Counter | Total number of requests that were rejected | `zone` | 378 | | `nginxplus_limit_request_delayed` | Counter | Total number of requests that were delayed | `zone` | 379 | | `nginxplus_limit_request_rejected_dry_run` | Counter | Total number of requests accounted as rejected in the dry run mode | `zone` | 380 | | `nginxplus_limit_request_delayed_dry_run` | Counter | Total number of requests accounted as delayed in the dry run mode | `zone` | 381 | 382 | #### [HTTP Connections Limiting](https://nginx.org/en/docs/http/ngx_http_api_module.html#def_nginx_http_limit_conn_zone) 383 | 384 | | Name | Type | Description | Labels | 385 | | --------------------------------------------- | ------- | ------------------------------------------------------------------------------ | ------ | 386 | | `nginxplus_limit_connection_passed` | Counter | Total number of connections that were neither limited nor accounted as limited | `zone` | 387 | | `nginxplus_limit_connection_rejected` | Counter | Total number of connections that were rejected | `zone` | 388 | | `nginxplus_limit_connection_rejected_dry_run` | Counter | Total number of connections accounted as rejected in the dry run mode | `zone` | 389 | 390 | #### [Stream Connections Limiting](https://nginx.org/en/docs/http/ngx_http_api_module.html#def_nginx_stream_limit_conn_zone) 391 | 392 | | Name | Type | Description | Labels | 393 | | ---------------------------------------------------- | ------- | ------------------------------------------------------------------------------ | ------ | 394 | | `nginxplus_stream_limit_connection_passed` | Counter | Total number of connections that were neither limited nor accounted as limited | `zone` | 395 | | `nginxplus_stream_limit_connection_rejected` | Counter | Total number of connections that were rejected | `zone` | 396 | | `nginxplus_stream_limit_connection_rejected_dry_run` | Counter | Total number of connections accounted as rejected in the dry run mode | `zone` | 397 | 398 | #### [Cache](https://nginx.org/en/docs/http/ngx_http_api_module.html#def_nginx_http_cache) 399 | 400 | | Name | Type | Description | Labels | 401 | | ------------------------------------------- | ------- | ----------------------------------------------------------------------- | ------- | 402 | | `nginxplus_cache_size` | Gauge | Total size of the cache | `cache` | 403 | | `nginxplus_cache_max_size` | Gauge | Maximum size of the cache | `cache` | 404 | | `nginxplus_cache_cold` | Gauge | Is the cache considered cold | `cache` | 405 | | `nginxplus_cache_hit_responses` | Counter | Total number of cache hits | `cache` | 406 | | `nginxplus_cache_hit_bytes` | Counter | Total number of bytes returned from cache hits | `cache` | 407 | | `nginxplus_cache_stale_responses` | Counter | Total number of stale cache hits | `cache` | 408 | | `nginxplus_cache_stale_bytes` | Counter | Total number of bytes returned from stale cache hits | `cache` | 409 | | `nginxplus_cache_updating_responses` | Counter | Total number of cache hits while cache is updating | `cache` | 410 | | `nginxplus_cache_updating_bytes` | Counter | Total number of bytes returned from cache while cache is updating | `cache` | 411 | | `nginxplus_cache_revalidated_responses` | Counter | Total number of cache revalidations | `cache` | 412 | | `nginxplus_cache_revalidated_bytes` | Counter | Total number of bytes returned from cache revalidations | `cache` | 413 | | `nginxplus_cache_miss_responses` | Counter | Total number of cache misses | `cache` | 414 | | `nginxplus_cache_miss_bytes` | Counter | Total number of bytes returned from cache misses | `cache` | 415 | | `nginxplus_cache_expired_responses` | Counter | Total number of cache hits with expired TTL | `cache` | 416 | | `nginxplus_cache_expired_bytes` | Counter | Total number of bytes returned from cache hits with expired TTL | `cache` | 417 | | `nginxplus_cache_expired_responses_written` | Counter | Total number of cache hits with expired TTL written to cache | `cache` | 418 | | `nginxplus_cache_expired_bytes_written` | Counter | Total number of bytes written to cache from cache hits with expired TTL | `cache` | 419 | | `nginxplus_cache_bypass_responses` | Counter | Total number of cache bypasses | `cache` | 420 | | `nginxplus_cache_bypass_bytes` | Counter | Total number of bytes returned from cache bypasses | `cache` | 421 | | `nginxplus_cache_bypass_responses_written` | Counter | Total number of cache bypasses written to cache | `cache` | 422 | | `nginxplus_cache_bypass_bytes_written` | Counter | Total number of bytes written to cache from cache bypasses | `cache` | 423 | 424 | #### [Worker](https://nginx.org/en/docs/http/ngx_http_api_module.html#def_nginx_worker) 425 | 426 | | Name | Type | Description | Labels | 427 | | ---------------------------------------- | ------- | ------------------------------------------------------------------------ | ----------- | 428 | | `nginxplus_worker_connection_accepted` | Counter | The total number of accepted client connections | `id`, `pid` | 429 | | `nginxplus_worker_connection_dropped` | Counter | The total number of dropped client connections | `id`, `pid` | 430 | | `nginxplus_worker_connection_active` | Gauge | The current number of active client connections | `id`, `pid` | 431 | | `nginxplus_worker_connection_idle` | Gauge | The current number of idle client connection | `id`, `pid` | 432 | | `nginxplus_worker_http_requests_total` | Counter | The total number of client requests received | `id`, `pid` | 433 | | `nginxplus_worker_http_requests_current` | Gauge | The current number of client requests that are currently being processed | `id`, `pid` | 434 | 435 | Connect to the `/metrics` page of the running exporter to see the complete list of metrics along with their 436 | descriptions. Note: to see server zones related metrics you must configure [status 437 | zones](https://nginx.org/en/docs/http/ngx_http_api_module.html#status_zone) and to see upstream related metrics you 438 | must configure upstreams with a [shared memory zone](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#zone). 439 | 440 | ## Troubleshooting 441 | 442 | The exporter logs errors to the standard output. When using Docker, if the exporter doesn’t work as expected, check its 443 | logs using [docker logs](https://docs.docker.com/engine/reference/commandline/logs/) command. 444 | 445 | ## Releases 446 | 447 | ### Docker images 448 | 449 | We publish the Docker image on [DockerHub](https://hub.docker.com/r/nginx/nginx-prometheus-exporter/), 450 | [GitHub Container](https://github.com/nginx/nginx-prometheus-exporter/pkgs/container/nginx-prometheus-exporter), 451 | [Amazon ECR Public Gallery](https://gallery.ecr.aws/nginx/nginx-prometheus-exporter) and 452 | [Quay.io](https://quay.io/repository/nginx/nginx-prometheus-exporter). 453 | 454 | As an alternative, you can choose the _edge_ version built from the [latest commit](https://github.com/nginx/nginx-prometheus-exporter/commits/main) 455 | from the main branch. The edge version is useful for experimenting with new features that are not yet published in a 456 | stable release. 457 | 458 | ### Binaries 459 | 460 | We publish the binaries for multiple Operating Systems and architectures on the GitHub [releases page](https://github.com/nginx/nginx-prometheus-exporter/releases). 461 | 462 | ### Homebrew 463 | 464 | You can add the NGINX homebrew tap with 465 | 466 | ```console 467 | brew tap nginx/tap 468 | ``` 469 | 470 | and then install the formula with 471 | 472 | ```console 473 | brew install nginx-prometheus-exporter 474 | ``` 475 | 476 | ### Snap 477 | 478 | You can install the NGINX Prometheus Exporter from the [Snap Store](https://snapcraft.io/nginx-prometheus-exporter). 479 | 480 | ```console 481 | snap install nginx-prometheus-exporter 482 | ``` 483 | 484 | ### Scoop 485 | 486 | You can add the NGINX Scoop bucket with 487 | 488 | ```console 489 | scoop bucket add nginx https://github.com/nginx/scoop-bucket.git 490 | ``` 491 | 492 | and then install the package with 493 | 494 | ```console 495 | scoop install nginx-prometheus-exporter 496 | ``` 497 | 498 | ### Nix 499 | 500 | First include NUR in your packageOverrides as explained in the [NUR documentation](https://github.com/nix-community/NUR#installation). 501 | 502 | Then you can use the exporter with the following command: 503 | 504 | ```console 505 | nix-shell --packages nur.repos.nginx.nginx-prometheus-exporter 506 | ``` 507 | 508 | or install it with: 509 | 510 | ```console 511 | nix-env -f '' -iA nur.repos.nginx.nginx-prometheus-exporter 512 | ``` 513 | 514 | ## Building the Exporter 515 | 516 | You can build the exporter using the provided Makefile. Before building the exporter, make sure the following software 517 | is installed on your machine: 518 | 519 | - make 520 | - git 521 | - Docker for building the container image 522 | - Go for building the binary 523 | 524 | ### Building the Docker Image 525 | 526 | To build the Docker image with the exporter, run: 527 | 528 | ```console 529 | make container 530 | ``` 531 | 532 | Note: go is not required, as the exporter binary is built in a Docker container. See the [Dockerfile](build/Dockerfile). 533 | 534 | ### Building the Binary 535 | 536 | To build the binary, run: 537 | 538 | ```console 539 | make 540 | ``` 541 | 542 | Note: the binary is built for the OS/arch of your machine. To build binaries for other platforms, see the 543 | [Makefile](Makefile). 544 | 545 | The binary is built with the name `nginx-prometheus-exporter`. 546 | 547 | ## Grafana Dashboard 548 | 549 | The official Grafana dashboard is provided with the exporter for NGINX. Check the [Grafana 550 | Dashboard](./grafana/README.md) documentation for more information. 551 | 552 | ## SBOM (Software Bill of Materials) 553 | 554 | We generate SBOMs for the binaries and the Docker image. 555 | 556 | ### Binaries 557 | 558 | The SBOMs for the binaries are available in the releases page. The SBOMs are generated using 559 | [syft](https://github.com/anchore/syft) and are available in SPDX format. 560 | 561 | ### Docker Image 562 | 563 | The SBOM for the Docker image is available in the 564 | [DockerHub](https://hub.docker.com/r/nginx/nginx-prometheus-exporter), 565 | [GitHub Container registry](https://github.com/nginx/nginx-prometheus-exporter/pkgs/container/nginx-prometheus-exporter), 566 | [Amazon ECR Public Gallery](https://gallery.ecr.aws/nginx/nginx-prometheus-exporter) and 567 | [Quay.io](https://quay.io/repository/nginx/nginx-prometheus-exporter) repositories. The SBOMs are generated using 568 | [syft](https://github.com/anchore/syft) and stored as an attestation in the image manifest. 569 | 570 | For example to retrieve the SBOM for `linux/amd64` from Docker Hub and analyze it using 571 | [grype](https://github.com/anchore/grype) you can run the following command: 572 | 573 | ```console 574 | docker buildx imagetools inspect nginx/nginx-prometheus-exporter:edge --format '{{ json (index .SBOM "linux/amd64").SPDX }}' | grype 575 | ``` 576 | 577 | ## Provenance 578 | 579 | We generate provenance for the Docker image and it's available in the 580 | [DockerHub](https://hub.docker.com/r/nginx/nginx-prometheus-exporter), 581 | [GitHub Container registry](https://github.com/nginx/nginx-prometheus-exporter/pkgs/container/nginx-prometheus-exporter), 582 | [Amazon ECR Public Gallery](https://gallery.ecr.aws/nginx/nginx-prometheus-exporter) and 583 | [Quay.io](https://quay.io/repository/nginx/nginx-prometheus-exporter) repositories, stored as an attestation in the 584 | image manifest. 585 | 586 | For example to retrieve the provenance for `linux/amd64` from Docker Hub you can run the following command: 587 | 588 | ```console 589 | docker buildx imagetools inspect nginx/nginx-prometheus-exporter:edge --format '{{ json (index .Provenance "linux/amd64").SLSA }}' 590 | ``` 591 | 592 | ## Contacts 593 | 594 | We’d like to hear your feedback! If you have any suggestions or experience issues with the NGINX Prometheus Exporter, 595 | please create an issue or send a pull request on GitHub. You can contact us on the [NGINX Community Forums](https://community.nginx.org/). 596 | 597 | ## Contributing 598 | 599 | If you'd like to contribute to the project, please read our [Contributing guide](CONTRIBUTING.md). 600 | 601 | ## Support 602 | 603 | The commercial support is available for NGINX Plus customers when the NGINX Prometheus Exporter is used with NGINX 604 | Ingress Controller. 605 | 606 | ## License 607 | 608 | [Apache License, Version 2.0](LICENSE). 609 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | We advise users to use the most recent release of the NGINX Prometheus Exporter. The commercial support is available for 6 | NGINX Plus customers when the NGINX Prometheus Exporter is used with NGINX Ingress Controller. 7 | 8 | ## Reporting a Vulnerability 9 | 10 | The F5 Security Incident Response Team (F5 SIRT) has an email alias that makes it easy to report potential security 11 | vulnerabilities. 12 | 13 | - If you’re an F5 customer with an active support contract, please contact [F5 Technical 14 | Support](https://www.f5.com/services/support). 15 | - If you aren’t an F5 customer, please report any potential or current instances of security vulnerabilities with any F5 16 | product to the F5 Security Incident Response Team at 17 | 18 | For more information visit 19 | -------------------------------------------------------------------------------- /build/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.15 2 | FROM golang:1.24 AS builder 3 | ARG VERSION 4 | ARG TARGETARCH 5 | 6 | WORKDIR /go/src/github.com/nginx/nginx-prometheus-exporter 7 | 8 | COPY --link go.mod go.sum ./ 9 | RUN go mod download 10 | 11 | COPY --link *.go ./ 12 | COPY --link collector ./collector 13 | COPY --link client ./client 14 | 15 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build -trimpath -a -ldflags "-s -w -X main.version=${VERSION}" -o nginx-prometheus-exporter . 16 | 17 | 18 | FROM --platform=$BUILDPLATFORM alpine:3.21 AS certs 19 | 20 | FROM scratch AS base 21 | COPY --from=certs --link /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 22 | USER 1001:1001 23 | ENTRYPOINT [ "/usr/bin/nginx-prometheus-exporter" ] 24 | 25 | 26 | FROM base AS container 27 | COPY --from=builder --link /go/src/github.com/nginx/nginx-prometheus-exporter/nginx-prometheus-exporter /usr/bin/ 28 | 29 | 30 | FROM base AS goreleaser 31 | ARG TARGETARCH 32 | ARG TARGETVARIANT 33 | ARG TARGETPLATFORM 34 | 35 | LABEL org.nginx.exporter.image.build.target="${TARGETPLATFORM}" 36 | LABEL org.nginx.exporter.image.build.version="goreleaser" 37 | 38 | COPY --link dist/nginx-prometheus-exporter_linux_$TARGETARCH${TARGETVARIANT/v/_}*/nginx-prometheus-exporter /usr/bin/ 39 | -------------------------------------------------------------------------------- /client/nginx.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | ) 10 | 11 | const templateMetrics string = `Active connections: %d 12 | server accepts handled requests 13 | %d %d %d 14 | Reading: %d Writing: %d Waiting: %d 15 | ` 16 | 17 | // NginxClient allows you to fetch NGINX metrics from the stub_status page. 18 | type NginxClient struct { 19 | httpClient *http.Client 20 | apiEndpoint string 21 | } 22 | 23 | // StubStats represents NGINX stub_status metrics. 24 | type StubStats struct { 25 | Connections StubConnections 26 | Requests int64 27 | } 28 | 29 | // StubConnections represents connections related metrics. 30 | type StubConnections struct { 31 | Active int64 32 | Accepted int64 33 | Handled int64 34 | Reading int64 35 | Writing int64 36 | Waiting int64 37 | } 38 | 39 | // NewNginxClient creates an NginxClient. 40 | func NewNginxClient(httpClient *http.Client, apiEndpoint string) *NginxClient { 41 | client := &NginxClient{ 42 | apiEndpoint: apiEndpoint, 43 | httpClient: httpClient, 44 | } 45 | 46 | return client 47 | } 48 | 49 | // GetStubStats fetches the stub_status metrics. 50 | func (client *NginxClient) GetStubStats() (*StubStats, error) { 51 | ctx, cancel := context.WithCancel(context.Background()) 52 | defer cancel() 53 | 54 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, client.apiEndpoint, nil) 55 | if err != nil { 56 | return nil, fmt.Errorf("failed to create a get request: %w", err) 57 | } 58 | resp, err := client.httpClient.Do(req) 59 | if err != nil { 60 | return nil, fmt.Errorf("failed to get %v: %w", client.apiEndpoint, err) 61 | } 62 | defer resp.Body.Close() 63 | 64 | if resp.StatusCode != http.StatusOK { 65 | return nil, fmt.Errorf("expected %v response, got %v", http.StatusOK, resp.StatusCode) 66 | } 67 | 68 | body, err := io.ReadAll(resp.Body) 69 | if err != nil { 70 | return nil, fmt.Errorf("failed to read the response body: %w", err) 71 | } 72 | 73 | r := bytes.NewReader(body) 74 | stats, err := parseStubStats(r) 75 | if err != nil { 76 | return nil, fmt.Errorf("failed to parse response body %q: %w", string(body), err) 77 | } 78 | 79 | return stats, nil 80 | } 81 | 82 | func parseStubStats(r io.Reader) (*StubStats, error) { 83 | var s StubStats 84 | if _, err := fmt.Fscanf(r, templateMetrics, 85 | &s.Connections.Active, 86 | &s.Connections.Accepted, 87 | &s.Connections.Handled, 88 | &s.Requests, 89 | &s.Connections.Reading, 90 | &s.Connections.Writing, 91 | &s.Connections.Waiting); err != nil { 92 | return nil, fmt.Errorf("failed to scan template metrics: %w", err) 93 | } 94 | return &s, nil 95 | } 96 | -------------------------------------------------------------------------------- /client/nginx_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | const validStabStats = "Active connections: 1457 \nserver accepts handled requests\n 6717066 6717066 65844359 \nReading: 1 Writing: 8 Waiting: 1448 \n" 9 | 10 | func TestParseStubStatsValidInput(t *testing.T) { 11 | t.Parallel() 12 | 13 | tests := []struct { 14 | input []byte 15 | expectedResult StubStats 16 | expectedError bool 17 | }{ 18 | { 19 | input: []byte(validStabStats), 20 | expectedResult: StubStats{ 21 | Connections: StubConnections{ 22 | Active: 1457, 23 | Accepted: 6717066, 24 | Handled: 6717066, 25 | Reading: 1, 26 | Writing: 8, 27 | Waiting: 1448, 28 | }, 29 | Requests: 65844359, 30 | }, 31 | expectedError: false, 32 | }, 33 | { 34 | input: []byte("invalid-stats"), 35 | expectedError: true, 36 | }, 37 | } 38 | 39 | for _, test := range tests { 40 | r := bytes.NewReader(test.input) 41 | result, err := parseStubStats(r) 42 | 43 | if err != nil && !test.expectedError { 44 | t.Errorf("parseStubStats() returned error for valid input %q: %v", string(test.input), err) 45 | } 46 | 47 | if !test.expectedError && test.expectedResult != *result { 48 | t.Errorf("parseStubStats() result %v != expected %v for input %q", result, test.expectedResult, test.input) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /collector/helper.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus" 5 | ) 6 | 7 | const ( 8 | nginxUp = 1 9 | nginxDown = 0 10 | ) 11 | 12 | func newGlobalMetric(namespace string, metricName string, docString string, constLabels map[string]string) *prometheus.Desc { 13 | return prometheus.NewDesc(namespace+"_"+metricName, docString, nil, constLabels) 14 | } 15 | 16 | func newUpMetric(namespace string, constLabels map[string]string) prometheus.Gauge { 17 | return prometheus.NewGauge(prometheus.GaugeOpts{ 18 | Namespace: namespace, 19 | Name: "up", 20 | Help: "Status of the last metric scrape", 21 | ConstLabels: constLabels, 22 | }) 23 | } 24 | 25 | // MergeLabels merges two maps of labels. 26 | func MergeLabels(a map[string]string, b map[string]string) map[string]string { 27 | c := make(map[string]string) 28 | 29 | for k, v := range a { 30 | c[k] = v 31 | } 32 | for k, v := range b { 33 | c[k] = v 34 | } 35 | 36 | return c 37 | } 38 | -------------------------------------------------------------------------------- /collector/helper_test.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestMergeLabels(t *testing.T) { 9 | t.Parallel() 10 | 11 | tests := []struct { 12 | mapA map[string]string 13 | mapB map[string]string 14 | want map[string]string 15 | name string 16 | }{ 17 | { 18 | name: "base case", 19 | mapA: map[string]string{"a": "is here"}, 20 | mapB: map[string]string{"b": "is here"}, 21 | want: map[string]string{"a": "is here", "b": "is here"}, 22 | }, 23 | { 24 | name: "overwrite key case", 25 | mapA: map[string]string{"a": "is here"}, 26 | mapB: map[string]string{"b": "is here", "a": "is now here"}, 27 | want: map[string]string{"a": "is now here", "b": "is here"}, 28 | }, 29 | { 30 | name: "empty maps case", 31 | mapA: nil, 32 | mapB: nil, 33 | want: map[string]string{}, 34 | }, 35 | } 36 | for _, tt := range tests { 37 | t.Run(tt.name, func(t *testing.T) { 38 | t.Parallel() 39 | if got := MergeLabels(tt.mapA, tt.mapB); !reflect.DeepEqual(got, tt.want) { 40 | t.Errorf("mergeLabels() = %v, want %v", got, tt.want) 41 | } 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /collector/nginx.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "log/slog" 5 | "sync" 6 | 7 | "github.com/nginx/nginx-prometheus-exporter/client" 8 | "github.com/prometheus/client_golang/prometheus" 9 | ) 10 | 11 | // NginxCollector collects NGINX metrics. It implements prometheus.Collector interface. 12 | type NginxCollector struct { 13 | upMetric prometheus.Gauge 14 | logger *slog.Logger 15 | nginxClient *client.NginxClient 16 | metrics map[string]*prometheus.Desc 17 | mutex sync.Mutex 18 | } 19 | 20 | // NewNginxCollector creates an NginxCollector. 21 | func NewNginxCollector(nginxClient *client.NginxClient, namespace string, constLabels map[string]string, logger *slog.Logger) *NginxCollector { 22 | return &NginxCollector{ 23 | nginxClient: nginxClient, 24 | logger: logger, 25 | metrics: map[string]*prometheus.Desc{ 26 | "connections_active": newGlobalMetric(namespace, "connections_active", "Active client connections", constLabels), 27 | "connections_accepted": newGlobalMetric(namespace, "connections_accepted", "Accepted client connections", constLabels), 28 | "connections_handled": newGlobalMetric(namespace, "connections_handled", "Handled client connections", constLabels), 29 | "connections_reading": newGlobalMetric(namespace, "connections_reading", "Connections where NGINX is reading the request header", constLabels), 30 | "connections_writing": newGlobalMetric(namespace, "connections_writing", "Connections where NGINX is writing the response back to the client", constLabels), 31 | "connections_waiting": newGlobalMetric(namespace, "connections_waiting", "Idle client connections", constLabels), 32 | "http_requests_total": newGlobalMetric(namespace, "http_requests_total", "Total http requests", constLabels), 33 | }, 34 | upMetric: newUpMetric(namespace, constLabels), 35 | } 36 | } 37 | 38 | // Describe sends the super-set of all possible descriptors of NGINX metrics 39 | // to the provided channel. 40 | func (c *NginxCollector) Describe(ch chan<- *prometheus.Desc) { 41 | ch <- c.upMetric.Desc() 42 | 43 | for _, m := range c.metrics { 44 | ch <- m 45 | } 46 | } 47 | 48 | // Collect fetches metrics from NGINX and sends them to the provided channel. 49 | func (c *NginxCollector) Collect(ch chan<- prometheus.Metric) { 50 | c.mutex.Lock() // To protect metrics from concurrent collects 51 | defer c.mutex.Unlock() 52 | 53 | stats, err := c.nginxClient.GetStubStats() 54 | if err != nil { 55 | c.upMetric.Set(nginxDown) 56 | ch <- c.upMetric 57 | c.logger.Error("error getting stats", "error", err.Error()) 58 | return 59 | } 60 | 61 | c.upMetric.Set(nginxUp) 62 | ch <- c.upMetric 63 | 64 | ch <- prometheus.MustNewConstMetric(c.metrics["connections_active"], 65 | prometheus.GaugeValue, float64(stats.Connections.Active)) 66 | ch <- prometheus.MustNewConstMetric(c.metrics["connections_accepted"], 67 | prometheus.CounterValue, float64(stats.Connections.Accepted)) 68 | ch <- prometheus.MustNewConstMetric(c.metrics["connections_handled"], 69 | prometheus.CounterValue, float64(stats.Connections.Handled)) 70 | ch <- prometheus.MustNewConstMetric(c.metrics["connections_reading"], 71 | prometheus.GaugeValue, float64(stats.Connections.Reading)) 72 | ch <- prometheus.MustNewConstMetric(c.metrics["connections_writing"], 73 | prometheus.GaugeValue, float64(stats.Connections.Writing)) 74 | ch <- prometheus.MustNewConstMetric(c.metrics["connections_waiting"], 75 | prometheus.GaugeValue, float64(stats.Connections.Waiting)) 76 | ch <- prometheus.MustNewConstMetric(c.metrics["http_requests_total"], 77 | prometheus.CounterValue, float64(stats.Requests)) 78 | } 79 | -------------------------------------------------------------------------------- /examples/basic_auth/README.md: -------------------------------------------------------------------------------- 1 | # NGINX Prometheus Exporter with Web Configuration for Basic Authentication 2 | 3 | This example shows how to run NGINX Prometheus Exporter with web configuration. In this folder you will find an example 4 | configuration `web-config.yml` that enables basic authentication. It is configured to have a single user `alice` with 5 | password `password`. 6 | 7 | The full documentation for the web configuration can be found 8 | [here](https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md). 9 | 10 | 11 | 12 | ## Table of Contents 13 | 14 | - [Prerequisites](#prerequisites) 15 | - [Running NGINX Prometheus Exporter with Web Configuration in Basic Authentication mode](#running-nginx-prometheus-exporter-with-web-configuration-in-basic-authentication-mode) 16 | - [Verification](#verification) 17 | 18 | 19 | 20 | ## Prerequisites 21 | 22 | - NGINX Prometheus Exporter binary. See the [main README](../../README.md) for installation instructions. 23 | - NGINX or NGINX Plus running on the same machine. 24 | 25 | ## Running NGINX Prometheus Exporter with Web Configuration in Basic Authentication mode 26 | 27 | You can run NGINX Prometheus Exporter with web configuration in Basic Authentication mode using the following command: 28 | 29 | ```console 30 | nginx-prometheus-exporter --web.config.file=web-config.yml --nginx.scrape-uri="http://127.0.0.1:8080/stub_status" 31 | ``` 32 | 33 | Depending on your environment, you may need to specify the full path to the binary or change the path to the web 34 | configuration file. 35 | 36 | ## Verification 37 | 38 | Run `curl -u alice:password http://localhost:9113/metrics` to see the metrics exposed by the exporter. Without the `-u` 39 | flag, the request will fail with `401 Unauthorized`. 40 | -------------------------------------------------------------------------------- /examples/basic_auth/web-config.yml: -------------------------------------------------------------------------------- 1 | basic_auth_users: 2 | alice: $2y$10$6xfhlaIhUDCUl60zPxkqLudN3QjL3Lfjg5gPAWiqElTLErpxAxJbC 3 | -------------------------------------------------------------------------------- /examples/kubernetes/README.md: -------------------------------------------------------------------------------- 1 | # NGINX Prometheus Exporter in Kubernetes 2 | 3 | This example shows how to run NGINX Prometheus Exporter in a Kubernetes cluster. 4 | 5 | 6 | 7 | ## Table of Contents 8 | 9 | - [Prerequisites](#prerequisites) 10 | - [Create a kind cluster](#create-a-kind-cluster) 11 | - [Deploy the NGINX Hello application and NGINX Prometheus Exporter](#deploy-the-nginx-hello-application-and-nginx-prometheus-exporter) 12 | - [Configure port forwarding](#configure-port-forwarding) 13 | - [Verification](#verification) 14 | 15 | 16 | 17 | ## Prerequisites 18 | 19 | - [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) installed. 20 | - [kind](https://kind.sigs.k8s.io/) installed. 21 | 22 | ## Create a kind cluster 23 | 24 | You can create a kind cluster with the following command: 25 | 26 | ```console 27 | kind create cluster 28 | ``` 29 | 30 | For details, see the [kind documentation](https://kind.sigs.k8s.io/docs/user/quick-start/#creating-a-cluster). 31 | 32 | ## Deploy the NGINX Hello application and NGINX Prometheus Exporter 33 | 34 | You can deploy the NGINX Hello application and NGINX Prometheus Exporter with the following command: 35 | 36 | ```console 37 | kubectl apply -f nginx-hello.yaml 38 | ``` 39 | 40 | ## Configure port forwarding 41 | 42 | Port forwarding is used to access the NGINX Hello application and NGINX Prometheus Exporter from your local machine. 43 | 44 | You can configure port forwarding with the following command: 45 | 46 | ```console 47 | kubectl port-forward service/nginx-demo 8080:80 9113:9113 48 | ``` 49 | 50 | ## Verification 51 | 52 | You can access the NGINX Hello application at [http://localhost:8080](http://localhost:8080) and the 53 | NGINX Prometheus Exporter at [http://localhost:9113](http://localhost:9113). 54 | -------------------------------------------------------------------------------- /examples/kubernetes/nginx-hello.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: nginx-demo 6 | name: nginx-demo 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app.kubernetes.io/name: nginx-demo 12 | template: 13 | metadata: 14 | labels: 15 | app.kubernetes.io/name: nginx-demo 16 | annotations: 17 | prometheus.io/scrape: "true" 18 | prometheus.io/port: "9113" 19 | spec: 20 | containers: 21 | - image: nginxdemos/hello:latest 22 | name: nginx-demo 23 | ports: 24 | - name: http 25 | containerPort: 80 26 | volumeMounts: 27 | - name: config-volume 28 | mountPath: /etc/nginx/conf.d/status.conf 29 | subPath: status.conf 30 | - image: nginx/nginx-prometheus-exporter:latest 31 | name: nginx-prometheus-exporter 32 | args: 33 | - "--nginx.scrape-uri=http://localhost:8080/stub_status" 34 | ports: 35 | - name: metrics 36 | containerPort: 9113 37 | volumes: 38 | - name: config-volume 39 | configMap: 40 | name: status-config 41 | --- 42 | apiVersion: v1 43 | kind: Service 44 | metadata: 45 | name: nginx-demo 46 | spec: 47 | type: NodePort 48 | selector: 49 | app.kubernetes.io/name: nginx-demo 50 | ports: 51 | - port: 80 52 | targetPort: 80 53 | protocol: TCP 54 | name: http 55 | - port: 9113 56 | targetPort: 9113 57 | name: metrics 58 | --- 59 | apiVersion: v1 60 | kind: ConfigMap 61 | metadata: 62 | name: status-config 63 | data: 64 | status.conf: |- 65 | server { 66 | listen 8080; 67 | 68 | location /stub_status { 69 | stub_status; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /examples/systemd/README.md: -------------------------------------------------------------------------------- 1 | # NGINX Prometheus Exporter with systemd-activated socket 2 | 3 | This example shows how to run NGINX Prometheus Exporter with systemd-activated socket. 4 | 5 | 6 | 7 | ## Table of Contents 8 | 9 | - [Prerequisites](#prerequisites) 10 | - [Customization](#customization) 11 | - [Installation](#installation) 12 | - [Verification](#verification) 13 | 14 | 15 | 16 | ## Prerequisites 17 | 18 | - Linux machine with [systemd](https://www.freedesktop.org/wiki/Software/systemd/). 19 | - NGINX Prometheus Exporter binary in `/usr/local/bin/nginx-prometheus-exporter` or a location of your choice. See the 20 | [main README](../../README.md) for installation instructions. 21 | - NGINX or NGINX Plus running on the same machine. 22 | 23 | ## Customization 24 | 25 | Modify `nginx_exporter.service` and `nginx_exporter.socket` to match your environment. 26 | 27 | The default configuration assumes that NGINX Prometheus Exporter binary is located in 28 | `/usr/local/bin/nginx-prometheus-exporter`. 29 | 30 | The `ExecStart` directive has the flag `--web.systemd-socket` which tells the exporter to listen on the socket specified 31 | in the `nginx_exporter.socket` file. 32 | 33 | The `ListenStream` directive in `nginx_exporter.socket` specifies the socket to listen on. The default configuration 34 | uses `9113` port, but the address can be written in various formats, for example `/run/nginx_exporter.sock`. To see the 35 | full list of supported formats, run `man systemd.socket`. 36 | 37 | ## Installation 38 | 39 | 1. Copy `nginx_exporter.service` and `nginx_exporter.socket` to `/etc/systemd/system/` 40 | 2. Add a user named `nginx_exporter` 41 | 3. Run `systemctl daemon-reload` 42 | 4. Run `systemctl start nginx_exporter` 43 | 5. Run `systemctl status nginx_exporter` to check the status of the service 44 | 45 | ## Verification 46 | 47 | 1. Run `curl http://localhost:9113/metrics` to see the metrics exposed by the exporter. 48 | -------------------------------------------------------------------------------- /examples/systemd/nginx_exporter.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=NGINX Prometheus Exporter 3 | Requires=nginx_exporter.socket 4 | 5 | [Service] 6 | User=nginx_exporter 7 | ExecStart=/usr/local/bin/nginx-prometheus-exporter --nginx.scrape-uri=http://127.0.0.1:8080/stub_status --web.systemd-socket 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /examples/systemd/nginx_exporter.socket: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=NGINX Prometheus Exporter 3 | 4 | [Socket] 5 | ListenStream=9113 6 | 7 | [Install] 8 | WantedBy=sockets.target 9 | -------------------------------------------------------------------------------- /examples/tls/README.md: -------------------------------------------------------------------------------- 1 | # NGINX Prometheus Exporter with Web Configuration for TLS 2 | 3 | This example shows how to run NGINX Prometheus Exporter with web configuration. In this folder you will find an example 4 | configuration `web-config.yml` that enables TLS and specifies the path to the TLS certificate and key files. 5 | Additionally, there are two example TLS files `server.crt` and `server.key` that are used in the configuration. 6 | 7 | The full documentation for the web configuration can be found 8 | [here](https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md). 9 | 10 | 11 | 12 | ## Table of Contents 13 | 14 | - [Prerequisites](#prerequisites) 15 | - [Running NGINX Prometheus Exporter with Web Configuration in TLS mode](#running-nginx-prometheus-exporter-with-web-configuration-in-tls-mode) 16 | - [Verification](#verification) 17 | 18 | 19 | 20 | ## Prerequisites 21 | 22 | - NGINX Prometheus Exporter binary. See the [main README](../../README.md) for installation instructions. 23 | - NGINX or NGINX Plus running on the same machine. 24 | 25 | ## Running NGINX Prometheus Exporter with Web Configuration in TLS mode 26 | 27 | You can run NGINX Prometheus Exporter with web configuration in TLS mode using the following command: 28 | 29 | ```console 30 | nginx-prometheus-exporter --web.config.file=web-config.yml --nginx.scrape-uri="http://127.0.0.1:8080/stub_status" 31 | ``` 32 | 33 | you should see an output similar to this: 34 | 35 | ```console 36 | ... 37 | ts=2023-07-20T02:00:26.932Z caller=tls_config.go:274 level=info msg="Listening on" address=[::]:9113 38 | ts=2023-07-20T02:00:26.936Z caller=tls_config.go:310 level=info msg="TLS is enabled." http2=true address=[::]:9113 39 | ``` 40 | 41 | Depending on your environment, you may need to specify the full path to the binary or change the path to the web 42 | configuration file. 43 | 44 | ## Verification 45 | 46 | Run `curl -k https://localhost:9113/metrics` to see the metrics exposed by the exporter. The `-k` flag is needed because 47 | the certificate is self-signed. 48 | -------------------------------------------------------------------------------- /examples/tls/web-config.yml: -------------------------------------------------------------------------------- 1 | tls_server_config: 2 | cert_file: server.crt 3 | key_file: server.key 4 | -------------------------------------------------------------------------------- /exporter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "crypto/x509" 7 | "errors" 8 | "fmt" 9 | "log/slog" 10 | "maps" 11 | "net" 12 | "net/http" 13 | "os" 14 | "os/signal" 15 | "strings" 16 | "syscall" 17 | "time" 18 | 19 | plusclient "github.com/nginx/nginx-plus-go-client/v2/client" 20 | "github.com/nginx/nginx-prometheus-exporter/client" 21 | "github.com/nginx/nginx-prometheus-exporter/collector" 22 | 23 | "github.com/alecthomas/kingpin/v2" 24 | "github.com/prometheus/client_golang/prometheus" 25 | "github.com/prometheus/client_golang/prometheus/collectors/version" 26 | "github.com/prometheus/client_golang/prometheus/promhttp" 27 | "github.com/prometheus/common/promslog" 28 | "github.com/prometheus/common/promslog/flag" 29 | common_version "github.com/prometheus/common/version" 30 | 31 | "github.com/prometheus/exporter-toolkit/web" 32 | "github.com/prometheus/exporter-toolkit/web/kingpinflag" 33 | ) 34 | 35 | // positiveDuration is a wrapper of time.Duration to ensure only positive values are accepted. 36 | type positiveDuration struct{ time.Duration } 37 | 38 | func (pd *positiveDuration) Set(s string) error { 39 | dur, err := parsePositiveDuration(s) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | pd.Duration = dur.Duration 45 | return nil 46 | } 47 | 48 | func parsePositiveDuration(s string) (positiveDuration, error) { 49 | dur, err := time.ParseDuration(s) 50 | if err != nil { 51 | return positiveDuration{}, fmt.Errorf("failed to parse duration %q: %w", s, err) 52 | } 53 | if dur < 0 { 54 | return positiveDuration{}, fmt.Errorf("negative duration %v is not valid", dur) 55 | } 56 | return positiveDuration{dur}, nil 57 | } 58 | 59 | func createPositiveDurationFlag(s kingpin.Settings) (target *time.Duration) { 60 | target = new(time.Duration) 61 | s.SetValue(&positiveDuration{Duration: *target}) 62 | return 63 | } 64 | 65 | func parseUnixSocketAddress(address string) (string, string, error) { 66 | addressParts := strings.Split(address, ":") 67 | addressPartsLength := len(addressParts) 68 | 69 | if addressPartsLength > 3 || addressPartsLength < 1 { 70 | return "", "", errors.New("address for unix domain socket has wrong format") 71 | } 72 | 73 | unixSocketPath := addressParts[1] 74 | requestPath := "" 75 | if addressPartsLength == 3 { 76 | requestPath = addressParts[2] 77 | } 78 | return unixSocketPath, requestPath, nil 79 | } 80 | 81 | var ( 82 | constLabels = map[string]string{} 83 | 84 | // Command-line flags. 85 | webConfig = kingpinflag.AddFlags(kingpin.CommandLine, ":9113") 86 | metricsPath = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").Envar("TELEMETRY_PATH").String() 87 | nginxPlus = kingpin.Flag("nginx.plus", "Start the exporter for NGINX Plus. By default, the exporter is started for NGINX.").Default("false").Envar("NGINX_PLUS").Bool() 88 | scrapeURIs = kingpin.Flag("nginx.scrape-uri", "A URI or unix domain socket path for scraping NGINX or NGINX Plus metrics. For NGINX, the stub_status page must be available through the URI. For NGINX Plus -- the API. Repeatable for multiple URIs.").Default("http://127.0.0.1:8080/stub_status").Envar("SCRAPE_URI").HintOptions("http://127.0.0.1:8080/stub_status", "http://127.0.0.1:8080/api").Strings() 89 | sslVerify = kingpin.Flag("nginx.ssl-verify", "Perform SSL certificate verification.").Default("false").Envar("SSL_VERIFY").Bool() 90 | sslCaCert = kingpin.Flag("nginx.ssl-ca-cert", "Path to the PEM encoded CA certificate file used to validate the servers SSL certificate.").Default("").Envar("SSL_CA_CERT").String() 91 | sslClientCert = kingpin.Flag("nginx.ssl-client-cert", "Path to the PEM encoded client certificate file to use when connecting to the server.").Default("").Envar("SSL_CLIENT_CERT").String() 92 | sslClientKey = kingpin.Flag("nginx.ssl-client-key", "Path to the PEM encoded client certificate key file to use when connecting to the server.").Default("").Envar("SSL_CLIENT_KEY").String() 93 | 94 | // Custom command-line flags. 95 | timeout = createPositiveDurationFlag(kingpin.Flag("nginx.timeout", "A timeout for scraping metrics from NGINX or NGINX Plus.").Default("5s").Envar("TIMEOUT").HintOptions("5s", "10s", "30s", "1m", "5m")) 96 | ) 97 | 98 | const exporterName = "nginx_exporter" 99 | 100 | func main() { 101 | kingpin.Flag("prometheus.const-label", "Label that will be used in every metric. Format is label=value. It can be repeated multiple times.").Envar("CONST_LABELS").StringMapVar(&constLabels) 102 | 103 | // convert deprecated flags to new format 104 | for i, arg := range os.Args { 105 | if strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") && len(arg) > 2 { 106 | newArg := "-" + arg 107 | fmt.Printf("the flag format is deprecated and will be removed in a future release, please use the new format: %s\n", newArg) 108 | os.Args[i] = newArg 109 | } 110 | } 111 | 112 | config := &promslog.Config{} 113 | 114 | flag.AddFlags(kingpin.CommandLine, config) 115 | kingpin.Version(common_version.Print(exporterName)) 116 | kingpin.HelpFlag.Short('h') 117 | 118 | addMissingEnvironmentFlags(kingpin.CommandLine) 119 | 120 | kingpin.Parse() 121 | logger := promslog.New(config) 122 | 123 | logger.Info("nginx-prometheus-exporter", "version", common_version.Info()) 124 | logger.Info("build context", "build_context", common_version.BuildContext()) 125 | 126 | prometheus.MustRegister(version.NewCollector(exporterName)) 127 | 128 | if len(*scrapeURIs) == 0 { 129 | logger.Error("no scrape addresses provided") 130 | os.Exit(1) 131 | } 132 | 133 | // #nosec G402 134 | sslConfig := &tls.Config{InsecureSkipVerify: !*sslVerify} 135 | if *sslCaCert != "" { 136 | caCert, err := os.ReadFile(*sslCaCert) 137 | if err != nil { 138 | logger.Error("loading CA cert failed", "err", err.Error()) 139 | os.Exit(1) 140 | } 141 | sslCaCertPool := x509.NewCertPool() 142 | ok := sslCaCertPool.AppendCertsFromPEM(caCert) 143 | if !ok { 144 | logger.Error("parsing CA cert file failed.") 145 | os.Exit(1) 146 | } 147 | sslConfig.RootCAs = sslCaCertPool 148 | } 149 | 150 | if *sslClientCert != "" && *sslClientKey != "" { 151 | clientCert, err := tls.LoadX509KeyPair(*sslClientCert, *sslClientKey) 152 | if err != nil { 153 | logger.Error("loading client certificate failed", "error", err.Error()) 154 | os.Exit(1) 155 | } 156 | sslConfig.Certificates = []tls.Certificate{clientCert} 157 | } 158 | 159 | transport := &http.Transport{ 160 | TLSClientConfig: sslConfig, 161 | } 162 | 163 | if len(*scrapeURIs) == 1 { 164 | registerCollector(logger, transport, (*scrapeURIs)[0], constLabels) 165 | } else { 166 | for _, addr := range *scrapeURIs { 167 | // add scrape URI to const labels 168 | labels := maps.Clone(constLabels) 169 | labels["addr"] = addr 170 | 171 | registerCollector(logger, transport, addr, labels) 172 | } 173 | } 174 | 175 | http.Handle(*metricsPath, promhttp.Handler()) 176 | 177 | if *metricsPath != "/" && *metricsPath != "" { 178 | landingConfig := web.LandingConfig{ 179 | Name: "NGINX Prometheus Exporter", 180 | Description: "Prometheus Exporter for NGINX and NGINX Plus", 181 | HeaderColor: "#039900", 182 | Version: common_version.Info(), 183 | Links: []web.LandingLinks{ 184 | { 185 | Address: *metricsPath, 186 | Text: "Metrics", 187 | }, 188 | }, 189 | } 190 | landingPage, err := web.NewLandingPage(landingConfig) 191 | if err != nil { 192 | logger.Error("failed to create landing page", "error", err.Error()) 193 | os.Exit(1) 194 | } 195 | http.Handle("/", landingPage) 196 | } 197 | 198 | ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill, syscall.SIGTERM) 199 | defer cancel() 200 | 201 | srv := &http.Server{ 202 | ReadHeaderTimeout: 5 * time.Second, 203 | } 204 | 205 | go func() { 206 | if err := web.ListenAndServe(srv, webConfig, logger); err != nil { 207 | if errors.Is(err, http.ErrServerClosed) { 208 | logger.Info("HTTP server closed", "error", err.Error()) 209 | os.Exit(0) 210 | } 211 | logger.Error("HTTP server failed", "error", err.Error()) 212 | os.Exit(1) 213 | } 214 | }() 215 | 216 | <-ctx.Done() 217 | logger.Info("shutting down") 218 | srvCtx, srvCancel := context.WithTimeout(context.Background(), 5*time.Second) 219 | defer srvCancel() 220 | _ = srv.Shutdown(srvCtx) 221 | } 222 | 223 | func registerCollector(logger *slog.Logger, transport *http.Transport, 224 | addr string, labels map[string]string, 225 | ) { 226 | if strings.HasPrefix(addr, "unix:") { 227 | socketPath, requestPath, err := parseUnixSocketAddress(addr) 228 | if err != nil { 229 | logger.Error("parsing unix domain socket scrape address failed", "uri", addr, "error", err.Error()) 230 | os.Exit(1) 231 | } 232 | 233 | transport.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) { 234 | return net.Dial("unix", socketPath) 235 | } 236 | addr = "http://unix" + requestPath 237 | } 238 | 239 | userAgent := fmt.Sprintf("NGINX-Prometheus-Exporter/v%v", common_version.Version) 240 | 241 | httpClient := &http.Client{ 242 | Timeout: *timeout, 243 | Transport: &userAgentRoundTripper{ 244 | agent: userAgent, 245 | rt: transport, 246 | }, 247 | } 248 | 249 | if *nginxPlus { 250 | plusClient, err := plusclient.NewNginxClient(addr, plusclient.WithHTTPClient(httpClient)) 251 | if err != nil { 252 | logger.Error("could not create Nginx Plus Client", "error", err.Error()) 253 | os.Exit(1) 254 | } 255 | variableLabelNames := collector.NewVariableLabelNames(nil, nil, nil, nil, nil, nil, nil) 256 | prometheus.MustRegister(collector.NewNginxPlusCollector(plusClient, "nginxplus", variableLabelNames, labels, logger)) 257 | } else { 258 | ossClient := client.NewNginxClient(httpClient, addr) 259 | prometheus.MustRegister(collector.NewNginxCollector(ossClient, "nginx", labels, logger)) 260 | } 261 | } 262 | 263 | type userAgentRoundTripper struct { 264 | rt http.RoundTripper 265 | agent string 266 | } 267 | 268 | func (rt *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 269 | req = cloneRequest(req) 270 | req.Header.Set("User-Agent", rt.agent) 271 | roundTrip, err := rt.rt.RoundTrip(req) 272 | if err != nil { 273 | return nil, fmt.Errorf("round trip failed: %w", err) 274 | } 275 | return roundTrip, nil 276 | } 277 | 278 | func cloneRequest(req *http.Request) *http.Request { 279 | r := new(http.Request) 280 | *r = *req // shallow clone 281 | 282 | // deep copy headers 283 | r.Header = make(http.Header, len(req.Header)) 284 | for key, values := range req.Header { 285 | newValues := make([]string, len(values)) 286 | copy(newValues, values) 287 | r.Header[key] = newValues 288 | } 289 | return r 290 | } 291 | 292 | // addMissingEnvironmentFlags sets Envar on any flag which has 293 | // the "web." prefix which doesn't already have an Envar set. 294 | func addMissingEnvironmentFlags(ka *kingpin.Application) { 295 | for _, f := range ka.Model().Flags { 296 | if strings.HasPrefix(f.Name, "web.") && f.Envar == "" { 297 | retrievedFlag := ka.GetFlag(f.Name) 298 | if retrievedFlag != nil { 299 | retrievedFlag.Envar(convertFlagToEnvar(strings.TrimPrefix(f.Name, "web."))) 300 | } 301 | } 302 | } 303 | } 304 | 305 | func convertFlagToEnvar(f string) string { 306 | env := strings.ToUpper(f) 307 | for _, s := range []string{"-", "."} { 308 | env = strings.ReplaceAll(env, s, "_") 309 | } 310 | return env 311 | } 312 | -------------------------------------------------------------------------------- /exporter_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | 8 | "github.com/alecthomas/kingpin/v2" 9 | "github.com/prometheus/exporter-toolkit/web/kingpinflag" 10 | ) 11 | 12 | func TestParsePositiveDuration(t *testing.T) { 13 | t.Parallel() 14 | 15 | tests := []struct { 16 | name string 17 | testInput string 18 | want positiveDuration 19 | wantErr bool 20 | }{ 21 | { 22 | "ParsePositiveDuration returns a positiveDuration", 23 | "15ms", 24 | positiveDuration{15 * time.Millisecond}, 25 | false, 26 | }, 27 | { 28 | "ParsePositiveDuration returns error for trying to parse negative value", 29 | "-15ms", 30 | positiveDuration{}, 31 | true, 32 | }, 33 | { 34 | "ParsePositiveDuration returns error for trying to parse empty string", 35 | "", 36 | positiveDuration{}, 37 | true, 38 | }, 39 | } 40 | for _, tt := range tests { 41 | t.Run(tt.name, func(t *testing.T) { 42 | t.Parallel() 43 | got, err := parsePositiveDuration(tt.testInput) 44 | if (err != nil) != tt.wantErr { 45 | t.Errorf("parsePositiveDuration() error = %v, wantErr %v", err, tt.wantErr) 46 | return 47 | } 48 | if !reflect.DeepEqual(got, tt.want) { 49 | t.Errorf("parsePositiveDuration() = %v, want %v", got, tt.want) 50 | } 51 | }) 52 | } 53 | } 54 | 55 | func TestParseUnixSocketAddress(t *testing.T) { 56 | t.Parallel() 57 | 58 | tests := []struct { 59 | name string 60 | testInput string 61 | wantSocketPath string 62 | wantRequestPath string 63 | wantErr bool 64 | }{ 65 | { 66 | "Normal unix socket address", 67 | "unix:/path/to/socket", 68 | "/path/to/socket", 69 | "", 70 | false, 71 | }, 72 | { 73 | "Normal unix socket address with location", 74 | "unix:/path/to/socket:/with/location", 75 | "/path/to/socket", 76 | "/with/location", 77 | false, 78 | }, 79 | { 80 | "Unix socket address with trailing ", 81 | "unix:/trailing/path:", 82 | "/trailing/path", 83 | "", 84 | false, 85 | }, 86 | { 87 | "Unix socket address with too many colons", 88 | "unix:/too:/many:colons:", 89 | "", 90 | "", 91 | true, 92 | }, 93 | } 94 | for _, tt := range tests { 95 | t.Run(tt.name, func(t *testing.T) { 96 | t.Parallel() 97 | socketPath, requestPath, err := parseUnixSocketAddress(tt.testInput) 98 | if (err != nil) != tt.wantErr { 99 | t.Errorf("parseUnixSocketAddress() error = %v, wantErr %v", err, tt.wantErr) 100 | return 101 | } 102 | if !reflect.DeepEqual(socketPath, tt.wantSocketPath) { 103 | t.Errorf("socket path: parseUnixSocketAddress() = %v, want %v", socketPath, tt.wantSocketPath) 104 | } 105 | if !reflect.DeepEqual(requestPath, tt.wantRequestPath) { 106 | t.Errorf("request path: parseUnixSocketAddress() = %v, want %v", requestPath, tt.wantRequestPath) 107 | } 108 | }) 109 | } 110 | } 111 | 112 | func TestAddMissingEnvironmentFlags(t *testing.T) { 113 | t.Parallel() 114 | expectedMatches := map[string]string{ 115 | "non-matching-flag": "", 116 | "web.missing-env": "MISSING_ENV", 117 | "web.has-env": "HAS_ENV_ALREADY", 118 | "web.listen-address": "LISTEN_ADDRESS", 119 | "web.config.file": "CONFIG_FILE", 120 | } 121 | kingpinflag.AddFlags(kingpin.CommandLine, ":9113") 122 | kingpin.Flag("non-matching-flag", "").String() 123 | kingpin.Flag("web.missing-env", "").String() 124 | kingpin.Flag("web.has-env", "").Envar("HAS_ENV_ALREADY").String() 125 | addMissingEnvironmentFlags(kingpin.CommandLine) 126 | 127 | // using Envar() on a flag returned from GetFlag() 128 | // adds an additional flag, which is processed correctly 129 | // at runtime but means that we need to check for a match 130 | // instead of checking the envar of each matching flag name 131 | for k, v := range expectedMatches { 132 | matched := false 133 | for _, f := range kingpin.CommandLine.Model().Flags { 134 | if f.Name == k && f.Envar == v { 135 | matched = true 136 | } 137 | } 138 | if !matched { 139 | t.Errorf("missing %s envar for %s", v, k) 140 | } 141 | } 142 | } 143 | 144 | func TestConvertFlagToEnvar(t *testing.T) { 145 | t.Parallel() 146 | cases := []struct { 147 | input string 148 | output string 149 | }{ 150 | { 151 | input: "dot.separate", 152 | output: "DOT_SEPARATE", 153 | }, 154 | { 155 | input: "underscore_separate", 156 | output: "UNDERSCORE_SEPARATE", 157 | }, 158 | { 159 | input: "mixed_separate_options", 160 | output: "MIXED_SEPARATE_OPTIONS", 161 | }, 162 | } 163 | 164 | for _, c := range cases { 165 | res := convertFlagToEnvar(c.input) 166 | if res != c.output { 167 | t.Errorf("expected %s to resolve to %s but got %s", c.input, c.output, res) 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nginx/nginx-prometheus-exporter 2 | 3 | go 1.24.2 4 | 5 | require ( 6 | github.com/alecthomas/kingpin/v2 v2.4.0 7 | github.com/nginx/nginx-plus-go-client/v2 v2.4.0 8 | github.com/prometheus/client_golang v1.22.0 9 | github.com/prometheus/common v0.63.0 10 | github.com/prometheus/exporter-toolkit v0.14.0 11 | ) 12 | 13 | require ( 14 | github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect 15 | github.com/beorn7/perks v1.0.1 // indirect 16 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 17 | github.com/coreos/go-systemd/v22 v22.5.0 // indirect 18 | github.com/jpillora/backoff v1.0.0 // indirect 19 | github.com/mdlayher/socket v0.4.1 // indirect 20 | github.com/mdlayher/vsock v1.2.1 // indirect 21 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 22 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect 23 | github.com/prometheus/client_model v0.6.1 // indirect 24 | github.com/prometheus/procfs v0.15.1 // indirect 25 | github.com/xhit/go-str2duration/v2 v2.1.0 // indirect 26 | golang.org/x/crypto v0.36.0 // indirect 27 | golang.org/x/net v0.38.0 // indirect 28 | golang.org/x/oauth2 v0.27.0 // indirect 29 | golang.org/x/sync v0.13.0 // indirect 30 | golang.org/x/sys v0.31.0 // indirect 31 | golang.org/x/text v0.23.0 // indirect 32 | google.golang.org/protobuf v1.36.5 // indirect 33 | gopkg.in/yaml.v2 v2.4.0 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= 2 | github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= 3 | github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= 4 | github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= 5 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 6 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 7 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 8 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 9 | github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= 10 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 11 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 15 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 16 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 17 | github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= 18 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 19 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 20 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 21 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 22 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 23 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 24 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 25 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 26 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 27 | github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= 28 | github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= 29 | github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= 30 | github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= 31 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 32 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 33 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= 34 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 35 | github.com/nginx/nginx-plus-go-client/v2 v2.4.0 h1:4c7V57CLCZUOxQCUcS9G8a5MClzdmxByBm+f4zKMzAY= 36 | github.com/nginx/nginx-plus-go-client/v2 v2.4.0/go.mod h1:P+dIP2oKYzFoyf/zlLWQa8Sf+fHb+CclOKzxAjxpvug= 37 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 38 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 39 | github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= 40 | github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= 41 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 42 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 43 | github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= 44 | github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= 45 | github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg= 46 | github.com/prometheus/exporter-toolkit v0.14.0/go.mod h1:Gu5LnVvt7Nr/oqTBUC23WILZepW0nffNo10XdhQcwWA= 47 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 48 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 49 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 50 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 51 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 52 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 53 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 54 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 55 | github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= 56 | github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= 57 | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= 58 | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 59 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 60 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 61 | golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= 62 | golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 63 | golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= 64 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 65 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 66 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 67 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 68 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 69 | google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= 70 | google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 71 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 72 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 73 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 74 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 75 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 76 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 77 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 78 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 79 | -------------------------------------------------------------------------------- /grafana/README.md: -------------------------------------------------------------------------------- 1 | # Grafana Dashboard 2 | 3 | We provide the official Grafana dashboard that visualizes the NGINX metrics exposed by the exporter. The dashboard 4 | allows you to filter metrics per instance or see the metrics from all instances. 5 | 6 | 7 | 8 | ## Table of Contents 9 | 10 | - [Prerequisites](#prerequisites) 11 | - [Installing the Dashboard](#installing-the-dashboard) 12 | - [Graphs](#graphs) 13 | 14 | 15 | 16 | ## Prerequisites 17 | 18 | The dashboard has been tested with the following software versions: 19 | 20 | - NGINX Prometheus Exporter >= 0.4.1 21 | - Grafana >= v5.0.0 22 | - Prometheus >= v2.0.0 23 | 24 | A Prometheus data source needs to be [added](https://prometheus.io/docs/visualization/grafana/#using) before installing 25 | the dashboard. 26 | 27 | ## Installing the Dashboard 28 | 29 | In the Grafana UI complete the following steps: 30 | 31 | 1. Use the *New Dashboard* button and click *Import*. 32 | 2. Upload `dashboard.json` or copy and paste the contents of the file in the textbox and click *Load*. 33 | 3. Set the Prometheus data source and click *Import*. 34 | 4. The dashboard will appear. Note how you can filter the instance label just below the dashboard title (top left 35 | corner). This allows you to filter metrics per instance. By default, all instances are selected. 36 | 37 | ![dashboard](./dashboard.png) 38 | 39 | ## Graphs 40 | 41 | The dashboard comes with 2 rows with the following graphs for NGINX metrics: 42 | 43 | - Status 44 | - Up/Down graph per instance. It shows the `nginx_up` metric. 45 | - Metrics 46 | - Processed connections (`nginx_connections_accepted` and `nginx_connections_handled` metrics). This graph shows an 47 | [irate](https://prometheus.io/docs/prometheus/latest/querying/functions/#irate) in a range of 5 minutes. Useful for 48 | seeing the variation of the processed connections in time. 49 | - Active connections (`nginx_connections_active`, `nginx_connections_reading`, `nginx_connections_waiting` and 50 | `nginx_connections_writing`). Useful for checking what is happening right now. 51 | - Total Requests with an irate (5 minutes range too) of the total number of client requests 52 | (`nginx_http_requests_total`) over time. 53 | -------------------------------------------------------------------------------- /grafana/dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "description": "", 5 | "label": "Prometheus", 6 | "name": "DS_PROMETHEUS", 7 | "pluginId": "prometheus", 8 | "pluginName": "Prometheus", 9 | "type": "datasource" 10 | } 11 | ], 12 | "__requires": [ 13 | { 14 | "id": "grafana", 15 | "name": "Grafana", 16 | "type": "grafana", 17 | "version": "5.0.0" 18 | }, 19 | { 20 | "id": "graph", 21 | "name": "Graph", 22 | "type": "panel", 23 | "version": "" 24 | }, 25 | { 26 | "id": "prometheus", 27 | "name": "Prometheus", 28 | "type": "datasource", 29 | "version": "1.0.0" 30 | }, 31 | { 32 | "id": "singlestat", 33 | "name": "Singlestat", 34 | "type": "panel", 35 | "version": "" 36 | } 37 | ], 38 | "annotations": { 39 | "list": [ 40 | { 41 | "builtIn": 1, 42 | "datasource": "-- Grafana --", 43 | "enable": true, 44 | "hide": true, 45 | "iconColor": "rgba(0, 211, 255, 1)", 46 | "name": "Annotations & Alerts", 47 | "type": "dashboard" 48 | } 49 | ] 50 | }, 51 | "description": "Official dashboard for NGINX Prometheus exporter", 52 | "editable": true, 53 | "gnetId": null, 54 | "graphTooltip": 0, 55 | "id": null, 56 | "iteration": 1562682051068, 57 | "links": [], 58 | "panels": [ 59 | { 60 | "collapsed": false, 61 | "datasource": "${DS_PROMETHEUS}", 62 | "gridPos": { 63 | "h": 1, 64 | "w": 24, 65 | "x": 0, 66 | "y": 0 67 | }, 68 | "id": 4, 69 | "panels": [], 70 | "title": "Status", 71 | "type": "row" 72 | }, 73 | { 74 | "cacheTimeout": null, 75 | "colorBackground": true, 76 | "colorPostfix": false, 77 | "colorPrefix": false, 78 | "colorValue": false, 79 | "colors": [ 80 | "#E02F44", 81 | "#FF9830", 82 | "#299c46" 83 | ], 84 | "datasource": "${DS_PROMETHEUS}", 85 | "decimals": null, 86 | "description": "", 87 | "format": "none", 88 | "gauge": { 89 | "maxValue": 100, 90 | "minValue": 0, 91 | "show": false, 92 | "thresholdLabels": false, 93 | "thresholdMarkers": true 94 | }, 95 | "gridPos": { 96 | "h": 3, 97 | "w": 12, 98 | "x": 0, 99 | "y": 1 100 | }, 101 | "id": 8, 102 | "interval": null, 103 | "links": [], 104 | "mappingType": 1, 105 | "mappingTypes": [ 106 | { 107 | "name": "value to text", 108 | "value": 1 109 | }, 110 | { 111 | "name": "range to text", 112 | "value": 2 113 | } 114 | ], 115 | "maxDataPoints": 100, 116 | "nullPointMode": "connected", 117 | "nullText": null, 118 | "options": {}, 119 | "postfix": "", 120 | "postfixFontSize": "50%", 121 | "prefix": "", 122 | "prefixFontSize": "50%", 123 | "rangeMaps": [ 124 | { 125 | "from": "null", 126 | "text": "N/A", 127 | "to": "null" 128 | } 129 | ], 130 | "repeat": "instance", 131 | "repeatDirection": "h", 132 | "sparkline": { 133 | "fillColor": "rgba(31, 118, 189, 0.18)", 134 | "full": false, 135 | "lineColor": "rgb(31, 120, 193)", 136 | "show": false 137 | }, 138 | "tableColumn": "", 139 | "targets": [ 140 | { 141 | "expr": "nginx_up{instance=~\"$instance\"}", 142 | "format": "time_series", 143 | "instant": false, 144 | "intervalFactor": 1, 145 | "refId": "A" 146 | } 147 | ], 148 | "thresholds": "1,1", 149 | "timeFrom": null, 150 | "timeShift": null, 151 | "title": "NGINX Status for $instance", 152 | "type": "singlestat", 153 | "valueFontSize": "100%", 154 | "valueMaps": [ 155 | { 156 | "op": "=", 157 | "text": "Down", 158 | "value": "0" 159 | }, 160 | { 161 | "op": "=", 162 | "text": "Up", 163 | "value": "1" 164 | } 165 | ], 166 | "valueName": "current" 167 | }, 168 | { 169 | "collapsed": false, 170 | "datasource": "${DS_PROMETHEUS}", 171 | "gridPos": { 172 | "h": 1, 173 | "w": 24, 174 | "x": 0, 175 | "y": 4 176 | }, 177 | "id": 6, 178 | "panels": [], 179 | "title": "Metrics", 180 | "type": "row" 181 | }, 182 | { 183 | "aliasColors": {}, 184 | "bars": false, 185 | "dashLength": 10, 186 | "dashes": false, 187 | "datasource": "${DS_PROMETHEUS}", 188 | "decimals": null, 189 | "description": "", 190 | "fill": 1, 191 | "gridPos": { 192 | "h": 10, 193 | "w": 12, 194 | "x": 0, 195 | "y": 5 196 | }, 197 | "id": 10, 198 | "legend": { 199 | "alignAsTable": false, 200 | "avg": false, 201 | "current": false, 202 | "hideEmpty": false, 203 | "max": false, 204 | "min": false, 205 | "rightSide": false, 206 | "show": true, 207 | "total": false, 208 | "values": false 209 | }, 210 | "lines": true, 211 | "linewidth": 1, 212 | "links": [], 213 | "nullPointMode": "null", 214 | "options": {}, 215 | "percentage": false, 216 | "pointradius": 2, 217 | "points": false, 218 | "renderer": "flot", 219 | "seriesOverrides": [], 220 | "spaceLength": 10, 221 | "stack": false, 222 | "steppedLine": false, 223 | "targets": [ 224 | { 225 | "expr": "irate(nginx_connections_accepted{instance=~\"$instance\"}[5m])", 226 | "format": "time_series", 227 | "instant": false, 228 | "intervalFactor": 1, 229 | "legendFormat": "{{instance}} accepted", 230 | "refId": "A" 231 | }, 232 | { 233 | "expr": "irate(nginx_connections_handled{instance=~\"$instance\"}[5m])", 234 | "format": "time_series", 235 | "instant": false, 236 | "intervalFactor": 1, 237 | "legendFormat": "{{instance}} handled", 238 | "refId": "B" 239 | } 240 | ], 241 | "thresholds": [], 242 | "timeFrom": null, 243 | "timeRegions": [], 244 | "timeShift": null, 245 | "title": "Processed connections", 246 | "tooltip": { 247 | "shared": true, 248 | "sort": 0, 249 | "value_type": "individual" 250 | }, 251 | "type": "graph", 252 | "xaxis": { 253 | "buckets": null, 254 | "mode": "time", 255 | "name": null, 256 | "show": true, 257 | "values": [] 258 | }, 259 | "yaxes": [ 260 | { 261 | "decimals": 1, 262 | "format": "short", 263 | "label": "Connections (rate)", 264 | "logBase": 1, 265 | "max": null, 266 | "min": null, 267 | "show": true 268 | }, 269 | { 270 | "format": "short", 271 | "label": "", 272 | "logBase": 1, 273 | "max": null, 274 | "min": null, 275 | "show": true 276 | } 277 | ], 278 | "yaxis": { 279 | "align": false, 280 | "alignLevel": null 281 | } 282 | }, 283 | { 284 | "aliasColors": {}, 285 | "bars": false, 286 | "dashLength": 10, 287 | "dashes": false, 288 | "datasource": "${DS_PROMETHEUS}", 289 | "decimals": 0, 290 | "fill": 1, 291 | "gridPos": { 292 | "h": 10, 293 | "w": 12, 294 | "x": 12, 295 | "y": 5 296 | }, 297 | "id": 12, 298 | "legend": { 299 | "alignAsTable": false, 300 | "avg": false, 301 | "current": false, 302 | "max": false, 303 | "min": false, 304 | "rightSide": false, 305 | "show": true, 306 | "total": false, 307 | "values": false 308 | }, 309 | "lines": true, 310 | "linewidth": 1, 311 | "links": [], 312 | "nullPointMode": "null", 313 | "options": {}, 314 | "percentage": false, 315 | "pointradius": 2, 316 | "points": false, 317 | "renderer": "flot", 318 | "seriesOverrides": [], 319 | "spaceLength": 10, 320 | "stack": false, 321 | "steppedLine": false, 322 | "targets": [ 323 | { 324 | "expr": "nginx_connections_active{instance=~\"$instance\"}", 325 | "format": "time_series", 326 | "intervalFactor": 1, 327 | "legendFormat": "{{instance}} active", 328 | "refId": "A" 329 | }, 330 | { 331 | "expr": "nginx_connections_reading{instance=~\"$instance\"}", 332 | "format": "time_series", 333 | "intervalFactor": 1, 334 | "legendFormat": "{{instance}} reading", 335 | "refId": "B" 336 | }, 337 | { 338 | "expr": "nginx_connections_waiting{instance=~\"$instance\"}", 339 | "format": "time_series", 340 | "intervalFactor": 1, 341 | "legendFormat": "{{instance}} waiting", 342 | "refId": "C" 343 | }, 344 | { 345 | "expr": "nginx_connections_writing{instance=~\"$instance\"}", 346 | "format": "time_series", 347 | "intervalFactor": 1, 348 | "legendFormat": "{{instance}} writing", 349 | "refId": "D" 350 | } 351 | ], 352 | "thresholds": [], 353 | "timeFrom": null, 354 | "timeRegions": [], 355 | "timeShift": null, 356 | "title": "Active Connections", 357 | "tooltip": { 358 | "shared": true, 359 | "sort": 0, 360 | "value_type": "individual" 361 | }, 362 | "type": "graph", 363 | "xaxis": { 364 | "buckets": null, 365 | "mode": "time", 366 | "name": null, 367 | "show": true, 368 | "values": [] 369 | }, 370 | "yaxes": [ 371 | { 372 | "decimals": 0, 373 | "format": "short", 374 | "label": "Connections", 375 | "logBase": 1, 376 | "max": null, 377 | "min": null, 378 | "show": true 379 | }, 380 | { 381 | "format": "short", 382 | "label": null, 383 | "logBase": 1, 384 | "max": null, 385 | "min": null, 386 | "show": true 387 | } 388 | ], 389 | "yaxis": { 390 | "align": false, 391 | "alignLevel": null 392 | } 393 | }, 394 | { 395 | "aliasColors": {}, 396 | "bars": false, 397 | "dashLength": 10, 398 | "dashes": false, 399 | "datasource": "${DS_PROMETHEUS}", 400 | "fill": 1, 401 | "gridPos": { 402 | "h": 8, 403 | "w": 24, 404 | "x": 0, 405 | "y": 15 406 | }, 407 | "id": 15, 408 | "legend": { 409 | "avg": false, 410 | "current": false, 411 | "max": false, 412 | "min": false, 413 | "show": true, 414 | "total": false, 415 | "values": false 416 | }, 417 | "lines": true, 418 | "linewidth": 1, 419 | "links": [], 420 | "nullPointMode": "null", 421 | "options": {}, 422 | "percentage": false, 423 | "pointradius": 2, 424 | "points": false, 425 | "renderer": "flot", 426 | "seriesOverrides": [], 427 | "spaceLength": 10, 428 | "stack": false, 429 | "steppedLine": false, 430 | "targets": [ 431 | { 432 | "expr": "irate(nginx_http_requests_total{instance=~\"$instance\"}[5m])", 433 | "format": "time_series", 434 | "intervalFactor": 1, 435 | "legendFormat": "{{instance}} total requests", 436 | "refId": "A" 437 | } 438 | ], 439 | "thresholds": [], 440 | "timeFrom": null, 441 | "timeRegions": [], 442 | "timeShift": null, 443 | "title": "Total requests", 444 | "tooltip": { 445 | "shared": true, 446 | "sort": 0, 447 | "value_type": "individual" 448 | }, 449 | "type": "graph", 450 | "xaxis": { 451 | "buckets": null, 452 | "mode": "time", 453 | "name": null, 454 | "show": true, 455 | "values": [] 456 | }, 457 | "yaxes": [ 458 | { 459 | "format": "short", 460 | "label": null, 461 | "logBase": 1, 462 | "max": null, 463 | "min": null, 464 | "show": true 465 | }, 466 | { 467 | "format": "short", 468 | "label": null, 469 | "logBase": 1, 470 | "max": null, 471 | "min": null, 472 | "show": true 473 | } 474 | ], 475 | "yaxis": { 476 | "align": false, 477 | "alignLevel": null 478 | } 479 | } 480 | ], 481 | "refresh": "5s", 482 | "schemaVersion": 18, 483 | "style": "dark", 484 | "tags": [ 485 | "nginx", 486 | "prometheus", 487 | "nginx prometheus exporter" 488 | ], 489 | "templating": { 490 | "list": [ 491 | { 492 | "current": { 493 | "selected": false, 494 | "tags": [], 495 | "text": "default", 496 | "value": "default" 497 | }, 498 | "hide": 0, 499 | "includeAll": false, 500 | "label": "datasource", 501 | "multi": false, 502 | "name": "DS_PROMETHEUS", 503 | "options": [], 504 | "query": "prometheus", 505 | "refresh": 1, 506 | "regex": "", 507 | "skipUrlSync": false, 508 | "type": "datasource" 509 | }, 510 | { 511 | "allValue": null, 512 | "current": {}, 513 | "datasource": "${DS_PROMETHEUS}", 514 | "definition": "label_values(nginx_up, instance)", 515 | "hide": 0, 516 | "includeAll": true, 517 | "label": "", 518 | "multi": true, 519 | "name": "instance", 520 | "options": [], 521 | "query": "label_values(nginx_up, instance)", 522 | "refresh": 1, 523 | "regex": "", 524 | "skipUrlSync": false, 525 | "sort": 0, 526 | "tagValuesQuery": "", 527 | "tags": [], 528 | "tagsQuery": "", 529 | "type": "query", 530 | "useTags": false 531 | } 532 | ] 533 | }, 534 | "time": { 535 | "from": "now-15m", 536 | "to": "now" 537 | }, 538 | "timepicker": { 539 | "refresh_intervals": [ 540 | "5s", 541 | "10s", 542 | "30s", 543 | "1m", 544 | "5m", 545 | "15m", 546 | "30m", 547 | "1h", 548 | "2h", 549 | "1d" 550 | ], 551 | "time_options": [ 552 | "5m", 553 | "15m", 554 | "1h", 555 | "6h", 556 | "12h", 557 | "24h", 558 | "2d", 559 | "7d", 560 | "30d" 561 | ] 562 | }, 563 | "timezone": "", 564 | "title": "NGINX", 565 | "uid": "MsjffzSZz", 566 | "version": 1 567 | } 568 | -------------------------------------------------------------------------------- /grafana/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginx/nginx-prometheus-exporter/9237ce63146ab59b2c5562d28089a529536c5863/grafana/dashboard.png -------------------------------------------------------------------------------- /release-process.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | This document outlines the steps involved in the release process for the NGINX Prometheus Exporter. 4 | 5 | 6 | 7 | ## Table of Contents 8 | 9 | - [Versioning](#versioning) 10 | - [Release Planning and Development](#release-planning-and-development) 11 | - [Releasing a New Version](#releasing-a-new-version) 12 | 13 | 14 | 15 | ## Versioning 16 | 17 | The project follows [Semantic Versioning](https://semver.org/). 18 | 19 | ## Release Planning and Development 20 | 21 | The features that will go into the next release are reflected in the 22 | corresponding [milestone](https://github.com/nginx/nginx-prometheus-exporter/milestones). Refer to 23 | the [Issue Lifecycle](/ISSUE_LIFECYCLE.md) document for information on issue creation and assignment to releases. 24 | 25 | ## Releasing a New Version 26 | 27 | 1. Create an issue to define and track release-related activities. Choose a title that follows the 28 | format `Release X.Y.Z`. 29 | 2. Stop merging any new work into the main branch. 30 | 3. Check the release draft under the [GitHub releases](https://github.com/nginx/nginx-prometheus-exporter/releases) page 31 | to ensure that everything is in order. 32 | 4. Create a PR to update the version in the `Makefile` and [README](README.md) to the new version, and any other necessary 33 | changes. 34 | 5. Once the PR is merged, create a new release tag in the format `vX.Y.Z`: 35 | 36 | ```bash 37 | git tag -a vX.Y.Z -m "Release vX.Y.Z" 38 | git push origin vX.Y.Z 39 | ``` 40 | 41 | As a result, the CI/CD pipeline will: 42 | 43 | - Build the Docker image and push it to the registries. 44 | - Publish the release to Snapcraft, Homebrew, Scoop, and Nix. 45 | - Create a GitHub release with the autogenerated changelog and artifacts attached. 46 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "github>nginx/k8s-common", 5 | "schedule:earlyMondays" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /scripts/completions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | rm -rf completions 4 | mkdir completions 5 | for shell in bash zsh; do 6 | go run . --completion-script-$shell >completions/nginx-prometheus-exporter.$shell 7 | done 8 | -------------------------------------------------------------------------------- /scripts/manpages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | rm -rf manpages 4 | mkdir manpages 5 | go run . --help-man | gzip -c -9 >manpages/nginx-prometheus-exporter.1.gz 6 | --------------------------------------------------------------------------------