[
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "### Issue Description\n\n### Checklist\n\n- [ ] Dependencies installed\n- [ ] No typos\n- [ ] Searched existing issues and docs\n\n### Expected behaviour\n\n### Actual behaviour\n\n### Steps to reproduce\n\n### Working code to debug\n\n```go\npackage main\n\nfunc main() {\n}\n```\n\n### Version/commit\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 60\n# Number of days of inactivity before a stale issue is closed\ndaysUntilClose: 30\n# Issues with these labels will never be considered stale\nexemptLabels:\n  - pinned\n  - security\n  - bug\n  - enhancement\n# Label to use when marking an issue as stale\nstaleLabel: stale\n# Comment to post when marking an issue as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed within a month if no further activity occurs.\n  Thank you for your contributions.\n# Comment to post when closing a stale issue. Set to `false` to disable\ncloseComment: false\n"
  },
  {
    "path": ".github/workflows/echo-contrib.yml",
    "content": "name: Run Tests\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\n  workflow_dispatch:\n\npermissions:\n  contents: read #  to fetch code (actions/checkout)\n\nenv:\n  # run coverage and benchmarks only with the latest Go version\n  LATEST_GO_VERSION: \"1.26\"\n\njobs:\n  test:\n    strategy:\n      matrix:\n        os: [ubuntu-latest, macos-latest, windows-latest]\n        # Each major Go release is supported until there are two newer major releases. https://golang.org/doc/devel/release.html#policy\n        # Echo CORE tests with last four major releases (unless there are pressing vulnerabilities)\n        # As we depend on MANY DIFFERENT libraries which of SOME support last 2 Go releases we could have situations when\n        # we derive from last four major releases promise.\n        go: [\"1.25\", \"1.26\"]\n    name: ${{ matrix.os }} @ Go ${{ matrix.go }}\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v6\n\n      - name: Set up Go ${{ matrix.go }}\n        uses: actions/setup-go@v6\n        with:\n          go-version: ${{ matrix.go }}\n\n      - name: Run Tests\n        run: go test -race --coverprofile=coverage.coverprofile --covermode=atomic ./...\n\n      - name: Upload coverage to Codecov\n        if: success() && matrix.go == env.LATEST_GO_VERSION && matrix.os == 'ubuntu-latest'\n        uses: codecov/codecov-action@v5\n        with:\n          token:\n          fail_ci_if_error: false\n\n  benchmark:\n    needs: test\n    name: Benchmark comparison\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout Code (Previous)\n        uses: actions/checkout@v6\n        with:\n          ref: ${{ github.base_ref }}\n          path: previous\n\n      - name: Checkout Code (New)\n        uses: actions/checkout@v6\n        with:\n          path: new\n\n      - name: Set up Go ${{ matrix.go }}\n        uses: actions/setup-go@v6\n        with:\n          go-version: ${{ env.LATEST_GO_VERSION }}\n\n      - name: Install Dependencies\n        run: go install golang.org/x/perf/cmd/benchstat@latest\n\n      - name: Run Benchmark (Previous)\n        run: |\n          cd previous\n          go test -run=\"-\" -bench=\".*\" -count=8 ./... > benchmark.txt\n\n      - name: Run Benchmark (New)\n        run: |\n          cd new\n          go test -run=\"-\" -bench=\".*\" -count=8 ./... > benchmark.txt\n\n      - name: Run Benchstat\n        run: |\n          benchstat previous/benchmark.txt new/benchmark.txt"
  },
  {
    "path": ".gitignore",
    "content": "vendor\n_test\ncoverage.txt\n.idea"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017 LabStack\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "PKG := \"github.com/labstack/echo-contrib\"\nPKG_LIST := $(shell go list ${PKG}/...)\n\n.DEFAULT_GOAL := check\ncheck: lint vet race ## Check project\n\ninit:\n\t@go install honnef.co/go/tools/cmd/staticcheck@latest\n\nformat: ## Format the source code\n\t@find ./ -type f -name \"*.go\" -exec gofmt -w {} \\;\n\nlint: ## Lint the files\n\t@staticcheck -tests=false ${PKG_LIST}\n\nvet: ## Vet the files\n\t@go vet ${PKG_LIST}\n\ntest: ## Run tests\n\t@go test -short ${PKG_LIST}\n\nrace: ## Run tests with data race detector\n\t@go test -race ${PKG_LIST}\n\nbenchmark: ## Run benchmarks\n\t@go test -run=\"-\" -bench=\".*\" ${PKG_LIST}\n\nhelp: ## Display this help screen\n\t@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n\ngoversion ?= \"1.18\"\ntest_version: ## Run tests inside Docker with given version (defaults to 1.18 oldest supported). Example: make test_version goversion=1.18\n\t@docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c \"cd /project && make race\"\n"
  },
  {
    "path": "README.md",
    "content": "# Echo Community Contribution middlewares\n\n [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/labstack/echo-contrib)\n [![Codecov](https://img.shields.io/codecov/c/github/labstack/echo-contrib.svg?style=flat-square)](https://codecov.io/gh/labstack/echo-contrib)\n [![Twitter](https://img.shields.io/badge/twitter-@labstack-55acee.svg?style=flat-square)](https://twitter.com/labstack)\n\n* [Official website](https://echo.labstack.com)\n* [All middleware docs](https://echo.labstack.com/docs/category/middleware)\n\n## Deprecations / alternatives\n\n1. Prometheus middleware has a new separate repository - https://github.com/labstack/echo-prometheus\n2. Jaeger middleware is deprecated, use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + [OTLP exporters](https://opentelemetry.io/docs/languages/go/exporters/).\n3. Zipkin middleware is deprecated, use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + [OTLP exporters](https://opentelemetry.io/docs/languages/go/exporters/).\n\n## Usage\n\nFor Echo `v5` support:\n```bash\ngo get github.com/labstack/echo-contrib/v5\n```\n\n## Versioning\n\nThis repository does not use semantic versioning. MAJOR version tracks which Echo version should be used. MINOR version\ntracks API changes (possibly backwards incompatible, which is a very rare occasion), and a PATCH version is incremented for fixes.\n\n> **Always add at least one integration test in your project.**\n\nMinimal needed Echo versions:\n\n* `v5.x.y` needs Echo `v5.0.0+`, use `go get github.com/labstack/echo-contrib/v5@latest`\n* `v0.18.0` needs Echo `v4.15.0+`, use `go get github.com/labstack/echo-contrib@v0`\n\nFor `v0.x.y` releases the code is located in `v4` branch.\n\n# Supported Go version\n\nEach major Go release is supported until there are two newer major releases. https://golang.org/doc/devel/release.html#policy\n\n\n[Echo CORE](https://github.com/labstack/echo) tests with last FOUR major releases (unless there are pressing vulnerabilities)\nAs this library depends on MANY DIFFERENT libraries which of SOME support only last 2 Go releases we could have situations when\nwe derive from last four major releases promise.\n\np.s. you really should use latest versions of Go as there are many vulnerebilites fixed only in supported versions. Please see https://pkg.go.dev/vuln/\n"
  },
  {
    "path": "casbin/README.md",
    "content": "# Usage\nSimple example:\n```go\npackage main\n\nimport (\n\t\"log/slog\"\n\n\t\"github.com/casbin/casbin/v2\"\n\tcasbin_mw \"github.com/labstack/echo-contrib/v5/casbin\"\n\t\"github.com/labstack/echo/v5\"\n)\n\nfunc main() {\n\te := echo.New()\n\n\t// Mediate the access for every request\n\tenforcer, err := casbin.NewEnforcer(\"auth_model.conf\", \"auth_policy.csv\")\n\tif err != nil {\n\t\tslog.Error(\"failed to load casbin enforcer\", \"error\", err)\n\t}\n\te.Use(casbin_mw.Middleware(enforcer))\n\n\tif err := e.Start(\":1323\"); err != nil {\n\t\tslog.Error(\"failed to start server\", \"error\", err)\n\t}\n}\n\n```\n\nAdvanced example:\n```go\npackage main\n\nimport (\n\t\"log/slog\"\n\n\t\"github.com/casbin/casbin/v2\"\n\tcasbin_mw \"github.com/labstack/echo-contrib/v5/casbin\"\n\t\"github.com/labstack/echo/v5\"\n)\n\nfunc main() {\n\tce, _ := casbin.NewEnforcer(\"auth_model.conf\", \"\")\n\tce.AddRoleForUser(\"alice\", \"admin\")\n\tce.AddPolicy(...)\n\n\te := echo.New()\n\n\te.Use(casbin_mw.Middleware(ce))\n\n\tif err := e.Start(\":1323\"); err != nil {\n\t\tslog.Error(\"failed to start server\", \"error\", err)\n\t}\n}\n```\n\n# API Reference\nSee [API Overview](https://casbin.org/docs/api-overview)."
  },
  {
    "path": "casbin/auth_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == \"*\")\n"
  },
  {
    "path": "casbin/auth_policy.csv",
    "content": "p, alice, /dataset1/*, GET\np, alice, /dataset1/resource1, POST\np, bob, /dataset2/resource1, *\np, bob, /dataset2/resource2, GET\np, bob, /dataset2/folder1/*, POST\np, dataset1_admin, /dataset1/*, *\ng, cathy, dataset1_admin\n"
  },
  {
    "path": "casbin/broken_auth_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == \"*\")\n"
  },
  {
    "path": "casbin/casbin.go",
    "content": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\n/* Package casbin provides middleware to enable ACL, RBAC, ABAC authorization support.\n\nSimple example:\n\n\tpackage main\n\n\timport (\n\t\t\"github.com/casbin/casbin/v2\"\n\t\t\"github.com/labstack/echo/v5\"\n\t\tcasbin_mw \"github.com/labstack/echo-contrib/v5/casbin\"\n\t)\n\n\tfunc main() {\n\t\te := echo.New()\n\n\t\t// Mediate the access for every request\n\t\te.Use(casbin_mw.Middleware(casbin.NewEnforcer(\"auth_model.conf\", \"auth_policy.csv\")))\n\n\t\te.Logger.Fatal(e.Start(\":1323\"))\n\t}\n\nAdvanced example:\n\n\tpackage main\n\n\timport (\n\t\t\"github.com/casbin/casbin/v2\"\n\t\t\"github.com/labstack/echo/v5\"\n\t\tcasbin_mw \"github.com/labstack/echo-contrib/v5/casbin\"\n\t)\n\n\tfunc main() {\n\t\tce, _ := casbin.NewEnforcer(\"auth_model.conf\", \"\")\n\t\tce.AddRoleForUser(\"alice\", \"admin\")\n\t\tce.AddPolicy(...)\n\n\t\te := echo.New()\n\n\t\te.Use(casbin_mw.Middleware(ce))\n\n\t\te.Logger.Fatal(e.Start(\":1323\"))\n\t}\n*/\n\npackage casbin\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\n\t\"github.com/casbin/casbin/v2\"\n\t\"github.com/labstack/echo/v5\"\n\t\"github.com/labstack/echo/v5/middleware\"\n)\n\ntype (\n\t// Config defines the config for CasbinAuth middleware.\n\tConfig struct {\n\t\t// Skipper defines a function to skip middleware.\n\t\tSkipper middleware.Skipper\n\n\t\t// Enforcer CasbinAuth main rule.\n\t\t// One of Enforcer or EnforceHandler fields is required.\n\t\tEnforcer *casbin.Enforcer\n\n\t\t// EnforceHandler is custom callback to handle enforcing.\n\t\t// One of Enforcer or EnforceHandler fields is required.\n\t\tEnforceHandler func(c *echo.Context, user string) (bool, error)\n\n\t\t// Method to get the username - defaults to using basic auth\n\t\tUserGetter func(c *echo.Context) (string, error)\n\n\t\t// Method to handle errors\n\t\tErrorHandler func(c *echo.Context, internal error, proposedStatus int) error\n\t}\n)\n\nvar (\n\t// DefaultConfig is the default CasbinAuth middleware config.\n\tDefaultConfig = Config{\n\t\tSkipper: middleware.DefaultSkipper,\n\t\tUserGetter: func(c *echo.Context) (string, error) {\n\t\t\tusername, _, _ := c.Request().BasicAuth()\n\t\t\treturn username, nil\n\t\t},\n\t\tErrorHandler: func(c *echo.Context, internal error, proposedStatus int) error {\n\t\t\treturn echo.NewHTTPError(proposedStatus, internal.Error()).Wrap(internal)\n\t\t},\n\t}\n)\n\n// Middleware returns a CasbinAuth middleware.\n//\n// For valid credentials it calls the next handler.\n// For missing or invalid credentials, it sends \"401 - Unauthorized\" response.\nfunc Middleware(ce *casbin.Enforcer) echo.MiddlewareFunc {\n\tc := DefaultConfig\n\tc.Enforcer = ce\n\treturn MiddlewareWithConfig(c)\n}\n\n// MiddlewareWithConfig returns a CasbinAuth middleware with config.\n// See `Middleware()`.\nfunc MiddlewareWithConfig(config Config) echo.MiddlewareFunc {\n\tif config.Enforcer == nil && config.EnforceHandler == nil {\n\t\tpanic(\"one of casbin middleware Enforcer or EnforceHandler fields must be set\")\n\t}\n\tif config.Skipper == nil {\n\t\tconfig.Skipper = DefaultConfig.Skipper\n\t}\n\tif config.UserGetter == nil {\n\t\tconfig.UserGetter = DefaultConfig.UserGetter\n\t}\n\tif config.ErrorHandler == nil {\n\t\tconfig.ErrorHandler = DefaultConfig.ErrorHandler\n\t}\n\tif config.EnforceHandler == nil {\n\t\tconfig.EnforceHandler = func(c *echo.Context, user string) (bool, error) {\n\t\t\treturn config.Enforcer.Enforce(user, c.Request().URL.Path, c.Request().Method)\n\t\t}\n\t}\n\n\treturn func(next echo.HandlerFunc) echo.HandlerFunc {\n\t\treturn func(c *echo.Context) error {\n\t\t\tif config.Skipper(c) {\n\t\t\t\treturn next(c)\n\t\t\t}\n\n\t\t\tuser, err := config.UserGetter(c)\n\t\t\tif err != nil {\n\t\t\t\treturn config.ErrorHandler(c, err, http.StatusForbidden)\n\t\t\t}\n\t\t\tpass, err := config.EnforceHandler(c, user)\n\t\t\tif err != nil {\n\t\t\t\treturn config.ErrorHandler(c, err, http.StatusInternalServerError)\n\t\t\t}\n\t\t\tif !pass {\n\t\t\t\treturn config.ErrorHandler(c, errors.New(\"enforce did not pass\"), http.StatusForbidden)\n\t\t\t}\n\t\t\treturn next(c)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "casbin/casbin_test.go",
    "content": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage casbin\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/casbin/casbin/v2\"\n\t\"github.com/labstack/echo/v5\"\n\t\"github.com/labstack/echo/v5/middleware\"\n)\n\nfunc testRequest(t *testing.T, h echo.HandlerFunc, user string, path string, method string, code int) {\n\te := echo.New()\n\treq := httptest.NewRequest(method, path, nil)\n\treq.SetBasicAuth(user, \"secret\")\n\tres := httptest.NewRecorder()\n\tc := e.NewContext(req, res)\n\n\terr := h(c)\n\n\tif err != nil {\n\t\tvar errObj *echo.HTTPError\n\t\tif errors.As(err, &errObj) {\n\t\t\tif errObj.Code != code {\n\t\t\t\tt.Errorf(\"%s, %s, %s: %d, supposed to be %d\", user, path, method, errObj.Code, code)\n\t\t\t}\n\t\t}\n\t} else {\n\t\tstatus := 0\n\t\tif eResp, uErr := echo.UnwrapResponse(c.Response()); uErr == nil {\n\t\t\tstatus = eResp.Status\n\t\t}\n\t\tif status != code {\n\t\t\tt.Errorf(\"%s, %s, %s: %d, supposed to be %d\", user, path, method, status, code)\n\t\t}\n\t}\n}\n\nfunc TestAuth(t *testing.T) {\n\tce, _ := casbin.NewEnforcer(\"auth_model.conf\", \"auth_policy.csv\")\n\th := Middleware(ce)(func(c *echo.Context) error {\n\t\treturn c.String(http.StatusOK, \"test\")\n\t})\n\n\ttestRequest(t, h, \"alice\", \"/dataset1/resource1\", http.MethodGet, http.StatusOK)\n\ttestRequest(t, h, \"alice\", \"/dataset1/resource1\", http.MethodPost, http.StatusOK)\n\ttestRequest(t, h, \"alice\", \"/dataset1/resource2\", http.MethodGet, http.StatusOK)\n\ttestRequest(t, h, \"alice\", \"/dataset1/resource2\", http.MethodPost, http.StatusForbidden)\n}\n\nfunc TestPathWildcard(t *testing.T) {\n\tce, _ := casbin.NewEnforcer(\"auth_model.conf\", \"auth_policy.csv\")\n\th := Middleware(ce)(func(c *echo.Context) error {\n\t\treturn c.String(http.StatusOK, \"test\")\n\t})\n\n\ttestRequest(t, h, \"bob\", \"/dataset2/resource1\", http.MethodGet, http.StatusOK)\n\ttestRequest(t, h, \"bob\", \"/dataset2/resource1\", http.MethodPost, http.StatusOK)\n\ttestRequest(t, h, \"bob\", \"/dataset2/resource1\", http.MethodDelete, http.StatusOK)\n\ttestRequest(t, h, \"bob\", \"/dataset2/resource2\", http.MethodGet, http.StatusOK)\n\ttestRequest(t, h, \"bob\", \"/dataset2/resource2\", http.MethodPost, http.StatusForbidden)\n\ttestRequest(t, h, \"bob\", \"/dataset2/resource2\", http.MethodDelete, http.StatusForbidden)\n\n\ttestRequest(t, h, \"bob\", \"/dataset2/folder1/item1\", http.MethodGet, http.StatusForbidden)\n\ttestRequest(t, h, \"bob\", \"/dataset2/folder1/item1\", http.MethodPost, http.StatusOK)\n\ttestRequest(t, h, \"bob\", \"/dataset2/folder1/item1\", http.MethodDelete, http.StatusForbidden)\n\ttestRequest(t, h, \"bob\", \"/dataset2/folder1/item2\", http.MethodGet, http.StatusForbidden)\n\ttestRequest(t, h, \"bob\", \"/dataset2/folder1/item2\", http.MethodPost, http.StatusOK)\n\ttestRequest(t, h, \"bob\", \"/dataset2/folder1/item2\", http.MethodDelete, http.StatusForbidden)\n}\n\nfunc TestRBAC(t *testing.T) {\n\tce, _ := casbin.NewEnforcer(\"auth_model.conf\", \"auth_policy.csv\")\n\th := Middleware(ce)(func(c *echo.Context) error {\n\t\treturn c.String(http.StatusOK, \"test\")\n\t})\n\n\t// cathy can access all /dataset1/* resources via all methods because it has the dataset1_admin role.\n\ttestRequest(t, h, \"cathy\", \"/dataset1/item\", http.MethodGet, http.StatusOK)\n\ttestRequest(t, h, \"cathy\", \"/dataset1/item\", http.MethodPost, http.StatusOK)\n\ttestRequest(t, h, \"cathy\", \"/dataset1/item\", http.MethodDelete, http.StatusOK)\n\ttestRequest(t, h, \"cathy\", \"/dataset2/item\", http.MethodGet, http.StatusForbidden)\n\ttestRequest(t, h, \"cathy\", \"/dataset2/item\", http.MethodPost, http.StatusForbidden)\n\ttestRequest(t, h, \"cathy\", \"/dataset2/item\", http.MethodDelete, http.StatusForbidden)\n\n\t// delete all roles on user cathy, so cathy cannot access any resources now.\n\tce.DeleteRolesForUser(\"cathy\")\n\n\ttestRequest(t, h, \"cathy\", \"/dataset1/item\", http.MethodGet, http.StatusForbidden)\n\ttestRequest(t, h, \"cathy\", \"/dataset1/item\", http.MethodPost, http.StatusForbidden)\n\ttestRequest(t, h, \"cathy\", \"/dataset1/item\", http.MethodDelete, http.StatusForbidden)\n\ttestRequest(t, h, \"cathy\", \"/dataset2/item\", http.MethodGet, http.StatusForbidden)\n\ttestRequest(t, h, \"cathy\", \"/dataset2/item\", http.MethodPost, http.StatusForbidden)\n\ttestRequest(t, h, \"cathy\", \"/dataset2/item\", http.MethodDelete, http.StatusForbidden)\n}\n\nfunc TestEnforceError(t *testing.T) {\n\tce, _ := casbin.NewEnforcer(\"broken_auth_model.conf\", \"auth_policy.csv\")\n\th := Middleware(ce)(func(c *echo.Context) error {\n\t\treturn c.String(http.StatusOK, \"test\")\n\t})\n\n\ttestRequest(t, h, \"cathy\", \"/dataset1/item\", http.MethodGet, http.StatusInternalServerError)\n}\n\nfunc TestCustomUserGetter(t *testing.T) {\n\tce, _ := casbin.NewEnforcer(\"auth_model.conf\", \"auth_policy.csv\")\n\tcnf := Config{\n\t\tSkipper:  middleware.DefaultSkipper,\n\t\tEnforcer: ce,\n\t\tUserGetter: func(c *echo.Context) (string, error) {\n\t\t\treturn \"not_cathy_at_all\", nil\n\t\t},\n\t}\n\th := MiddlewareWithConfig(cnf)(func(c *echo.Context) error {\n\t\treturn c.String(http.StatusOK, \"test\")\n\t})\n\ttestRequest(t, h, \"cathy\", \"/dataset1/item\", http.MethodGet, http.StatusForbidden)\n}\n\nfunc TestUserGetterError(t *testing.T) {\n\tce, _ := casbin.NewEnforcer(\"auth_model.conf\", \"auth_policy.csv\")\n\tcnf := Config{\n\t\tSkipper:  middleware.DefaultSkipper,\n\t\tEnforcer: ce,\n\t\tUserGetter: func(c *echo.Context) (string, error) {\n\t\t\treturn \"\", errors.New(\"no idea who you are\")\n\t\t},\n\t}\n\th := MiddlewareWithConfig(cnf)(func(c *echo.Context) error {\n\t\treturn c.String(http.StatusOK, \"test\")\n\t})\n\ttestRequest(t, h, \"cathy\", \"/dataset1/item\", http.MethodGet, http.StatusForbidden)\n}\n\nfunc TestCustomEnforceHandler(t *testing.T) {\n\tce, err := casbin.NewEnforcer(\"auth_model.conf\", \"auth_policy.csv\")\n\tassert.NoError(t, err)\n\n\t_, err = ce.AddPolicy(\"bob\", \"/user/bob\", \"PATCH_SELF\")\n\tassert.NoError(t, err)\n\n\tcnf := Config{\n\t\tEnforceHandler: func(c *echo.Context, user string) (bool, error) {\n\t\t\tmethod := c.Request().Method\n\t\t\tif strings.HasPrefix(c.Request().URL.Path, \"/user/bob\") {\n\t\t\t\tmethod += \"_SELF\"\n\t\t\t}\n\t\t\treturn ce.Enforce(user, c.Request().URL.Path, method)\n\t\t},\n\t}\n\th := MiddlewareWithConfig(cnf)(func(c *echo.Context) error {\n\t\treturn c.String(http.StatusOK, \"test\")\n\t})\n\ttestRequest(t, h, \"bob\", \"/dataset2/resource1\", http.MethodGet, http.StatusOK)\n\ttestRequest(t, h, \"bob\", \"/user/alice\", http.MethodPatch, http.StatusForbidden)\n\ttestRequest(t, h, \"bob\", \"/user/bob\", http.MethodPatch, http.StatusOK)\n}\n\nfunc TestCustomSkipper(t *testing.T) {\n\tce, _ := casbin.NewEnforcer(\"auth_model.conf\", \"auth_policy.csv\")\n\tcnf := Config{\n\t\tSkipper: func(c *echo.Context) bool {\n\t\t\treturn c.Request().URL.Path == \"/dataset1/resource1\"\n\t\t},\n\t\tEnforcer: ce,\n\t}\n\th := MiddlewareWithConfig(cnf)(func(c *echo.Context) error {\n\t\treturn c.String(http.StatusOK, \"test\")\n\t})\n\ttestRequest(t, h, \"alice\", \"/dataset1/resource1\", http.MethodGet, http.StatusOK)\n\ttestRequest(t, h, \"alice\", \"/dataset1/resource2\", http.MethodPost, http.StatusForbidden)\n}\n"
  },
  {
    "path": "codecov.yml",
    "content": "coverage:\n  status:\n    project:\n      default:\n        threshold: 1%\n    patch:\n      default:\n        threshold: 1%\n\ncomment:\n  require_changes: true"
  },
  {
    "path": "echo.go",
    "content": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage echo\n"
  },
  {
    "path": "echoprometheus/README.md",
    "content": "# Usage\n\nDeprecated: use new repository [echo-prometheus middleware](https://github.com/labstack/echo-prometheus) or [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters\n\n```go\npackage main\n\nimport (\n\t\"log/slog\"\n\n\t\"github.com/labstack/echo-contrib/v5/echoprometheus\"\n\t\"github.com/labstack/echo/v5\"\n)\n\nfunc main() {\n\te := echo.New()\n\t\n\t// Enable metrics middleware\n\te.Use(echoprometheus.NewMiddleware(\"myapp\"))\n\te.GET(\"/metrics\", echoprometheus.NewHandler())\n\n\tif err := e.Start(\":1323\"); err != nil {\n\t\tslog.Error(\"failed to start server\", \"error\", err)\n\t}\n}\n```\n"
  },
  {
    "path": "echoprometheus/prometheus.go",
    "content": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\n/*\nPackage echoprometheus provides middleware to add Prometheus metrics.\n\nDeprecated: use new repository [echo-prometheus middleware](https://github.com/labstack/echo-prometheus) or [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters\n*/\npackage echoprometheus\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/labstack/echo-contrib/v5/internal/helpers\"\n\t\"github.com/labstack/echo/v5\"\n\t\"github.com/labstack/echo/v5/middleware\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n\t\"github.com/prometheus/common/expfmt\"\n)\n\nconst (\n\tdefaultSubsystem = \"echo\"\n)\n\nconst (\n\t_           = iota // ignore first value by assigning to blank identifier\n\tbKB float64 = 1 << (10 * iota)\n\tbMB\n)\n\n// sizeBuckets is the buckets for request/response size. Here we define a spectrum from 1KB through 1NB up to 10MB.\nvar sizeBuckets = []float64{1.0 * bKB, 2.0 * bKB, 5.0 * bKB, 10.0 * bKB, 100 * bKB, 500 * bKB, 1.0 * bMB, 2.5 * bMB, 5.0 * bMB, 10.0 * bMB}\n\n// MiddlewareConfig contains the configuration for creating prometheus middleware collecting several default metrics.\ntype MiddlewareConfig struct {\n\t// Skipper defines a function to skip middleware.\n\tSkipper middleware.Skipper\n\n\t// Namespace is components of the fully-qualified name of the Metric (created by joining Namespace,Subsystem and Name components with \"_\")\n\t// Optional\n\tNamespace string\n\n\t// Subsystem is components of the fully-qualified name of the Metric (created by joining Namespace,Subsystem and Name components with \"_\")\n\t// Defaults to: \"echo\"\n\tSubsystem string\n\n\t// LabelFuncs allows adding custom labels in addition to default labels. When key has same name with default label\n\t// it replaces default one.\n\tLabelFuncs map[string]LabelValueFunc\n\n\t// HistogramOptsFunc allows to change options for metrics of type histogram before metric is registered to Registerer\n\tHistogramOptsFunc func(opts prometheus.HistogramOpts) prometheus.HistogramOpts\n\n\t// CounterOptsFunc allows to change options for metrics of type counter before metric is registered to Registerer\n\tCounterOptsFunc func(opts prometheus.CounterOpts) prometheus.CounterOpts\n\n\t// Registerer sets the prometheus.Registerer instance the middleware will register these metrics with.\n\t// Defaults to: prometheus.DefaultRegisterer\n\tRegisterer prometheus.Registerer\n\n\t// BeforeNext is callback that is executed before next middleware/handler is called. Useful for case when you have own\n\t// metrics that need data to be stored for AfterNext.\n\tBeforeNext func(c *echo.Context)\n\n\t// AfterNext is callback that is executed after next middleware/handler returns. Useful for case when you have own\n\t// metrics that need incremented/observed.\n\tAfterNext func(c *echo.Context, err error)\n\n\ttimeNow func() time.Time\n\n\t// If DoNotUseRequestPathFor404 is true, all 404 responses (due to non-matching route) will have the same `url` label and\n\t// thus won't generate new metrics.\n\tDoNotUseRequestPathFor404 bool\n\n\t// StatusCodeResolver resolves err & context into http status code. Default is to use context.Response().Status\n\tStatusCodeResolver func(c *echo.Context, err error) int\n}\n\ntype LabelValueFunc func(c *echo.Context, err error) string\n\n// HandlerConfig contains the configuration for creating HTTP handler for metrics.\ntype HandlerConfig struct {\n\t// Gatherer sets the prometheus.Gatherer instance the middleware will use when generating the metric endpoint handler.\n\t// Defaults to: prometheus.DefaultGatherer\n\tGatherer prometheus.Gatherer\n}\n\n// PushGatewayConfig contains the configuration for pushing to a Prometheus push gateway.\ntype PushGatewayConfig struct {\n\t// PushGatewayURL is push gateway URL in format http://domain:port\n\tPushGatewayURL string\n\n\t// PushInterval in ticker interval for pushing gathered metrics to the Gateway\n\t// Defaults to: 1 minute\n\tPushInterval time.Duration\n\n\t// Gatherer sets the prometheus.Gatherer instance the middleware will use when generating the metric endpoint handler.\n\t// Defaults to: prometheus.DefaultGatherer\n\tGatherer prometheus.Gatherer\n\n\t// ErrorHandler is function that is called when errors occur. When callback returns error StartPushGateway also returns.\n\tErrorHandler func(err error) error\n\n\t// ClientTransport specifies the mechanism by which individual HTTP POST requests are made.\n\t// Defaults to: http.DefaultTransport\n\tClientTransport http.RoundTripper\n}\n\n// NewHandler creates new instance of Handler using Prometheus default registry.\n//\n// Deprecated: use new repository [echo-prometheus middleware](https://github.com/labstack/echo-prometheus) or [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters\nfunc NewHandler() echo.HandlerFunc {\n\treturn NewHandlerWithConfig(HandlerConfig{})\n}\n\n// NewHandlerWithConfig creates new instance of Handler using given configuration.\nfunc NewHandlerWithConfig(config HandlerConfig) echo.HandlerFunc {\n\tif config.Gatherer == nil {\n\t\tconfig.Gatherer = prometheus.DefaultGatherer\n\t}\n\th := promhttp.HandlerFor(config.Gatherer, promhttp.HandlerOpts{DisableCompression: true})\n\n\tif r, ok := config.Gatherer.(prometheus.Registerer); ok {\n\t\th = promhttp.InstrumentMetricHandler(r, h)\n\t}\n\n\treturn func(c *echo.Context) error {\n\t\th.ServeHTTP(c.Response(), c.Request())\n\t\treturn nil\n\t}\n}\n\n// NewMiddleware creates new instance of middleware using Prometheus default registry.\n//\n// Deprecated: use new repository [echo-prometheus middleware](https://github.com/labstack/echo-prometheus) or [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters\nfunc NewMiddleware(subsystem string) echo.MiddlewareFunc {\n\treturn NewMiddlewareWithConfig(MiddlewareConfig{Subsystem: subsystem})\n}\n\n// NewMiddlewareWithConfig creates new instance of middleware using given configuration.\n//\n// Deprecated: use [echo-prometheus middleware](https://github.com/labstack/echo-prometheus) or [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters\nfunc NewMiddlewareWithConfig(config MiddlewareConfig) echo.MiddlewareFunc {\n\tmw, err := config.ToMiddleware()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn mw\n}\n\n// ToMiddleware converts configuration to middleware or returns an error.\n//\n// Deprecated: use new repository [echo-prometheus middleware](https://github.com/labstack/echo-prometheus) or [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters\nfunc (conf MiddlewareConfig) ToMiddleware() (echo.MiddlewareFunc, error) {\n\tif conf.timeNow == nil {\n\t\tconf.timeNow = time.Now\n\t}\n\tif conf.Subsystem == \"\" {\n\t\tconf.Subsystem = defaultSubsystem\n\t}\n\tif conf.Registerer == nil {\n\t\tconf.Registerer = prometheus.DefaultRegisterer\n\t}\n\tif conf.CounterOptsFunc == nil {\n\t\tconf.CounterOptsFunc = func(opts prometheus.CounterOpts) prometheus.CounterOpts {\n\t\t\treturn opts\n\t\t}\n\t}\n\tif conf.HistogramOptsFunc == nil {\n\t\tconf.HistogramOptsFunc = func(opts prometheus.HistogramOpts) prometheus.HistogramOpts {\n\t\t\treturn opts\n\t\t}\n\t}\n\tif conf.StatusCodeResolver == nil {\n\t\tconf.StatusCodeResolver = helpers.DefaultStatusResolver\n\t}\n\n\tlabelNames, customValuers := createLabels(conf.LabelFuncs)\n\n\trequestCount := prometheus.NewCounterVec(\n\t\tconf.CounterOptsFunc(prometheus.CounterOpts{\n\t\t\tNamespace: conf.Namespace,\n\t\t\tSubsystem: conf.Subsystem,\n\t\t\tName:      \"requests_total\",\n\t\t\tHelp:      \"How many HTTP requests processed, partitioned by status code and HTTP method.\",\n\t\t}),\n\t\tlabelNames,\n\t)\n\t// we do not allow skipping or replacing default collector but developer can use `conf.CounterOptsFunc` to rename\n\t// this middleware default collector, so they can have own collector with that same name.\n\t// and we treat all register errors as returnable failures\n\tif err := conf.Registerer.Register(requestCount); err != nil {\n\t\treturn nil, err\n\t}\n\n\trequestDuration := prometheus.NewHistogramVec(\n\t\tconf.HistogramOptsFunc(prometheus.HistogramOpts{\n\t\t\tNamespace: conf.Namespace,\n\t\t\tSubsystem: conf.Subsystem,\n\t\t\tName:      \"request_duration_seconds\",\n\t\t\tHelp:      \"The HTTP request latencies in seconds.\",\n\t\t\t// Here, we use the prometheus defaults which are for ~10s request length max: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}\n\t\t\tBuckets: prometheus.DefBuckets,\n\t\t}),\n\t\tlabelNames,\n\t)\n\tif rErr := conf.Registerer.Register(requestDuration); rErr != nil {\n\t\treturn nil, rErr\n\t}\n\n\tresponseSize := prometheus.NewHistogramVec(\n\t\tconf.HistogramOptsFunc(prometheus.HistogramOpts{\n\t\t\tNamespace: conf.Namespace,\n\t\t\tSubsystem: conf.Subsystem,\n\t\t\tName:      \"response_size_bytes\",\n\t\t\tHelp:      \"The HTTP response sizes in bytes.\",\n\t\t\tBuckets:   sizeBuckets,\n\t\t}),\n\t\tlabelNames,\n\t)\n\tif err := conf.Registerer.Register(responseSize); err != nil {\n\t\treturn nil, err\n\t}\n\n\trequestSize := prometheus.NewHistogramVec(\n\t\tconf.HistogramOptsFunc(prometheus.HistogramOpts{\n\t\t\tNamespace: conf.Namespace,\n\t\t\tSubsystem: conf.Subsystem,\n\t\t\tName:      \"request_size_bytes\",\n\t\t\tHelp:      \"The HTTP request sizes in bytes.\",\n\t\t\tBuckets:   sizeBuckets,\n\t\t}),\n\t\tlabelNames,\n\t)\n\tif rErr := conf.Registerer.Register(requestSize); rErr != nil {\n\t\treturn nil, rErr\n\t}\n\n\treturn func(next echo.HandlerFunc) echo.HandlerFunc {\n\t\treturn func(c *echo.Context) error {\n\t\t\t// NB: we do not skip metrics handler path by default. This can be added with custom Skipper but for default\n\t\t\t// behaviour we measure metrics path request/response metrics also\n\t\t\tif conf.Skipper != nil && conf.Skipper(c) {\n\t\t\t\treturn next(c)\n\t\t\t}\n\n\t\t\tif conf.BeforeNext != nil {\n\t\t\t\tconf.BeforeNext(c)\n\t\t\t}\n\t\t\treqSz := computeApproximateRequestSize(c.Request())\n\n\t\t\tstart := conf.timeNow()\n\t\t\terr := next(c)\n\t\t\telapsed := float64(conf.timeNow().Sub(start)) / float64(time.Second)\n\n\t\t\tif conf.AfterNext != nil {\n\t\t\t\tconf.AfterNext(c, err)\n\t\t\t}\n\n\t\t\turl := c.Path() // contains route path ala `/users/:id`\n\t\t\tif url == \"\" && !conf.DoNotUseRequestPathFor404 {\n\t\t\t\t// as of Echo v4.10.1 path is empty for 404 cases (when router did not find any matching routes)\n\t\t\t\t// in this case we use actual path from request to have some distinction in Prometheus\n\t\t\t\turl = c.Request().URL.Path\n\t\t\t}\n\n\t\t\tstatus := conf.StatusCodeResolver(c, err)\n\n\t\t\tvalues := make([]string, len(labelNames))\n\t\t\tvalues[0] = strconv.Itoa(status)\n\t\t\tvalues[1] = c.Request().Method\n\t\t\tvalues[2] = c.Request().Host\n\t\t\tvalues[3] = strings.ToValidUTF8(url, \"\\uFFFD\") // \\uFFFD is � https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character\n\t\t\tfor _, cv := range customValuers {\n\t\t\t\tvalues[cv.index] = cv.valueFunc(c, err)\n\t\t\t}\n\t\t\tif obs, err := requestDuration.GetMetricWithLabelValues(values...); err == nil {\n\t\t\t\tobs.Observe(elapsed)\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"failed to label request duration metric with values, err: %w\", err)\n\t\t\t}\n\t\t\tif obs, err := requestCount.GetMetricWithLabelValues(values...); err == nil {\n\t\t\t\tobs.Inc()\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"failed to label request count metric with values, err: %w\", err)\n\t\t\t}\n\t\t\tif obs, err := requestSize.GetMetricWithLabelValues(values...); err == nil {\n\t\t\t\tobs.Observe(float64(reqSz))\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"failed to label request size metric with values, err: %w\", err)\n\t\t\t}\n\t\t\tif obs, err := responseSize.GetMetricWithLabelValues(values...); err == nil {\n\t\t\t\tif eResp, uErr := echo.UnwrapResponse(c.Response()); uErr == nil {\n\t\t\t\t\tobs.Observe(float64(eResp.Size))\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"failed to label response size metric with values, err: %w\", err)\n\t\t\t}\n\n\t\t\treturn err\n\t\t}\n\t}, nil\n}\n\ntype customLabelValuer struct {\n\tindex     int\n\tlabel     string\n\tvalueFunc LabelValueFunc\n}\n\nfunc createLabels(customLabelFuncs map[string]LabelValueFunc) ([]string, []customLabelValuer) {\n\tlabelNames := []string{\"code\", \"method\", \"host\", \"url\"}\n\tif len(customLabelFuncs) == 0 {\n\t\treturn labelNames, nil\n\t}\n\n\tcustomValuers := make([]customLabelValuer, 0)\n\t// we create valuers in two passes for a reason - first to get fixed order, and then we know to assign correct indexes\n\tfor label, labelFunc := range customLabelFuncs {\n\t\tcustomValuers = append(customValuers, customLabelValuer{\n\t\t\tlabel:     label,\n\t\t\tvalueFunc: labelFunc,\n\t\t})\n\t}\n\tsort.Slice(customValuers, func(i, j int) bool {\n\t\treturn customValuers[i].label < customValuers[j].label\n\t})\n\n\tfor cvIdx, cv := range customValuers {\n\t\tidx := containsAt(labelNames, cv.label)\n\t\tif idx == -1 {\n\t\t\tidx = len(labelNames)\n\t\t\tlabelNames = append(labelNames, cv.label)\n\t\t}\n\t\tcustomValuers[cvIdx].index = idx\n\t}\n\treturn labelNames, customValuers\n}\n\nfunc containsAt[K comparable](haystack []K, needle K) int {\n\tfor i, v := range haystack {\n\t\tif v == needle {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc computeApproximateRequestSize(r *http.Request) int {\n\ts := 0\n\tif r.URL != nil {\n\t\ts = len(r.URL.Path)\n\t}\n\n\ts += len(r.Method)\n\ts += len(r.Proto)\n\tfor name, values := range r.Header {\n\t\ts += len(name)\n\t\tfor _, value := range values {\n\t\t\ts += len(value)\n\t\t}\n\t}\n\ts += len(r.Host)\n\n\t// N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.\n\n\tif r.ContentLength != -1 {\n\t\ts += int(r.ContentLength)\n\t}\n\treturn s\n}\n\n// RunPushGatewayGatherer starts pushing collected metrics and waits for it context to complete or ErrorHandler to return error.\n//\n// Example:\n// ```\n//\n//\tgo func() {\n//\t\tconfig := echoprometheus.PushGatewayConfig{\n//\t\t\tPushGatewayURL: \"https://host:9080\",\n//\t\t\tPushInterval:   10 * time.Millisecond,\n//\t\t}\n//\t\tif err := echoprometheus.RunPushGatewayGatherer(context.Background(), config); !errors.Is(err, context.Canceled) {\n//\t\t\tlog.Fatal(err)\n//\t\t}\n//\t}()\n//\n// ```\n//\n// Deprecated: use new repository [echo-prometheus middleware](https://github.com/labstack/echo-prometheus) or [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters\nfunc RunPushGatewayGatherer(ctx context.Context, config PushGatewayConfig) error {\n\tif config.PushGatewayURL == \"\" {\n\t\treturn errors.New(\"push gateway URL is missing\")\n\t}\n\tif config.PushInterval <= 0 {\n\t\tconfig.PushInterval = 1 * time.Minute\n\t}\n\tif config.Gatherer == nil {\n\t\tconfig.Gatherer = prometheus.DefaultGatherer\n\t}\n\tif config.ErrorHandler == nil {\n\t\tconfig.ErrorHandler = func(err error) error {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tclient := &http.Client{\n\t\tTransport: config.ClientTransport,\n\t}\n\tout := &bytes.Buffer{}\n\n\tticker := time.NewTicker(config.PushInterval)\n\tdefer ticker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tout.Reset()\n\t\t\terr := WriteGatheredMetrics(out, config.Gatherer)\n\t\t\tif err != nil {\n\t\t\t\tif hErr := config.ErrorHandler(fmt.Errorf(\"failed to create metrics: %w\", err)); hErr != nil {\n\t\t\t\t\treturn hErr\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, config.PushGatewayURL, out)\n\t\t\tif err != nil {\n\t\t\t\tif hErr := config.ErrorHandler(fmt.Errorf(\"failed to create push gateway request: %w\", err)); hErr != nil {\n\t\t\t\t\treturn hErr\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err != nil {\n\t\t\t\tif hErr := config.ErrorHandler(fmt.Errorf(\"error sending to push gateway: %w\", err)); hErr != nil {\n\t\t\t\t\treturn hErr\n\t\t\t\t}\n\t\t\t}\n\t\t\tif res.StatusCode != http.StatusOK {\n\t\t\t\tif hErr := config.ErrorHandler(echo.NewHTTPError(res.StatusCode, \"post metrics request did not succeed\")); hErr != nil {\n\t\t\t\t\treturn hErr\n\t\t\t\t}\n\t\t\t}\n\t\tcase <-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\t}\n\t}\n}\n\n// WriteGatheredMetrics gathers collected metrics and writes them to given writer\nfunc WriteGatheredMetrics(writer io.Writer, gatherer prometheus.Gatherer) error {\n\tmetricFamilies, err := gatherer.Gather()\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, mf := range metricFamilies {\n\t\tif _, err := expfmt.MetricFamilyToText(writer, mf); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "echoprometheus/prometheus_test.go",
    "content": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage echoprometheus\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/labstack/echo/v5\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestCustomRegistryMetrics(t *testing.T) {\n\te := echo.New()\n\n\tcustomRegistry := prometheus.NewRegistry()\n\te.Use(NewMiddlewareWithConfig(MiddlewareConfig{Registerer: customRegistry}))\n\te.GET(\"/metrics\", NewHandlerWithConfig(HandlerConfig{Gatherer: customRegistry}))\n\n\tassert.Equal(t, http.StatusNotFound, request(e, \"/ping?test=1\"))\n\n\ts, code := requestBody(e, \"/metrics\")\n\tassert.Equal(t, http.StatusOK, code)\n\tassert.Contains(t, s, `echo_request_duration_seconds_count{code=\"404\",host=\"example.com\",method=\"GET\",url=\"/ping\"} 1`)\n}\n\nfunc TestDefaultRegistryMetrics(t *testing.T) {\n\te := echo.New()\n\n\te.Use(NewMiddleware(\"myapp\"))\n\te.GET(\"/metrics\", NewHandler())\n\n\tassert.Equal(t, http.StatusNotFound, request(e, \"/ping?test=1\"))\n\n\ts, code := requestBody(e, \"/metrics\")\n\tassert.Equal(t, http.StatusOK, code)\n\tassert.Contains(t, s, `myapp_request_duration_seconds_count{code=\"404\",host=\"example.com\",method=\"GET\",url=\"/ping\"} 1`)\n\n\tunregisterDefaults(\"myapp\")\n}\n\nfunc TestPrometheus_Buckets(t *testing.T) {\n\te := echo.New()\n\n\tcustomRegistry := prometheus.NewRegistry()\n\te.Use(NewMiddlewareWithConfig(MiddlewareConfig{Registerer: customRegistry}))\n\te.GET(\"/metrics\", NewHandlerWithConfig(HandlerConfig{Gatherer: customRegistry}))\n\n\tassert.Equal(t, http.StatusNotFound, request(e, \"/ping\"))\n\n\tbody, code := requestBody(e, \"/metrics\")\n\tassert.Equal(t, http.StatusOK, code)\n\tassert.Contains(t, body, `echo_request_duration_seconds_bucket{code=\"404\",host=\"example.com\",method=\"GET\",url=\"/ping\",le=\"0.005\"}`, \"duration should have time bucket (like, 0.005s)\")\n\tassert.NotContains(t, body, `echo_request_duration_seconds_bucket{code=\"404\",host=\"example.com\",method=\"GET\",url=\"/ping\",le=\"512000\"}`, \"duration should NOT have a size bucket (like, 512K)\")\n\tassert.Contains(t, body, `echo_request_size_bytes_bucket{code=\"404\",host=\"example.com\",method=\"GET\",url=\"/ping\",le=\"1024\"}`, \"request size should have a 1024k (size) bucket\")\n\tassert.NotContains(t, body, `echo_request_size_bytes_bucket{code=\"404\",host=\"example.com\",method=\"GET\",url=\"/ping\",le=\"0.005\"}`, \"request size should NOT have time bucket (like, 0.005s)\")\n\tassert.Contains(t, body, `echo_response_size_bytes_bucket{code=\"404\",host=\"example.com\",method=\"GET\",url=\"/ping\",le=\"1024\"}`, \"response size should have a 1024k (size) bucket\")\n\tassert.NotContains(t, body, `echo_response_size_bytes_bucket{code=\"404\",host=\"example.com\",method=\"GET\",url=\"/ping\",le=\"0.005\"}`, \"response size should NOT have time bucket (like, 0.005s)\")\n}\n\nfunc TestMiddlewareConfig_Skipper(t *testing.T) {\n\te := echo.New()\n\n\tcustomRegistry := prometheus.NewRegistry()\n\te.Use(NewMiddlewareWithConfig(MiddlewareConfig{\n\t\tSkipper: func(c *echo.Context) bool {\n\t\t\thasSuffix := strings.HasSuffix(c.Path(), \"ignore\")\n\t\t\treturn hasSuffix\n\t\t},\n\t\tRegisterer: customRegistry,\n\t}))\n\n\te.GET(\"/test\", func(c *echo.Context) error {\n\t\treturn c.String(http.StatusOK, \"OK\")\n\t})\n\te.GET(\"/test_ignore\", func(c *echo.Context) error {\n\t\treturn c.String(http.StatusOK, \"OK\")\n\t})\n\n\tassert.Equal(t, http.StatusNotFound, request(e, \"/ping\"))\n\tassert.Equal(t, http.StatusOK, request(e, \"/test\"))\n\tassert.Equal(t, http.StatusOK, request(e, \"/test_ignore\"))\n\n\tout := &bytes.Buffer{}\n\tassert.NoError(t, WriteGatheredMetrics(out, customRegistry))\n\n\tbody := out.String()\n\tassert.Contains(t, body, `echo_request_duration_seconds_count{code=\"200\",host=\"example.com\",method=\"GET\",url=\"/test\"} 1`)\n\tassert.Contains(t, body, `echo_request_duration_seconds_count{code=\"404\",host=\"example.com\",method=\"GET\",url=\"/ping\"} 1`)\n\tassert.Contains(t, body, `echo_request_duration_seconds_count{code=\"404\",host=\"example.com\",method=\"GET\",url=\"/ping\"} 1`)\n\tassert.NotContains(t, body, `test_ignore`) // because we skipped\n}\n\nfunc TestMetricsForErrors(t *testing.T) {\n\te := echo.New()\n\tcustomRegistry := prometheus.NewRegistry()\n\te.Use(NewMiddlewareWithConfig(MiddlewareConfig{\n\t\tSkipper: func(c *echo.Context) bool {\n\t\t\treturn strings.HasSuffix(c.Path(), \"ignore\")\n\t\t},\n\t\tSubsystem:  \"myapp\",\n\t\tRegisterer: customRegistry,\n\t}))\n\te.GET(\"/metrics\", NewHandlerWithConfig(HandlerConfig{Gatherer: customRegistry}))\n\n\te.GET(\"/handler_for_ok\", func(c *echo.Context) error {\n\t\treturn c.JSON(http.StatusOK, \"OK\")\n\t})\n\te.GET(\"/handler_for_nok\", func(c *echo.Context) error {\n\t\treturn c.JSON(http.StatusConflict, \"NOK\")\n\t})\n\te.GET(\"/handler_for_error\", func(c *echo.Context) error {\n\t\treturn echo.NewHTTPError(http.StatusBadGateway, \"BAD\")\n\t})\n\n\tassert.Equal(t, http.StatusOK, request(e, \"/handler_for_ok\"))\n\tassert.Equal(t, http.StatusConflict, request(e, \"/handler_for_nok\"))\n\tassert.Equal(t, http.StatusConflict, request(e, \"/handler_for_nok\"))\n\tassert.Equal(t, http.StatusBadGateway, request(e, \"/handler_for_error\"))\n\n\tbody, code := requestBody(e, \"/metrics\")\n\tassert.Equal(t, http.StatusOK, code)\n\tassert.Contains(t, body, fmt.Sprintf(\"%s_requests_total\", \"myapp\"))\n\tassert.Contains(t, body, `myapp_requests_total{code=\"200\",host=\"example.com\",method=\"GET\",url=\"/handler_for_ok\"} 1`)\n\tassert.Contains(t, body, `myapp_requests_total{code=\"409\",host=\"example.com\",method=\"GET\",url=\"/handler_for_nok\"} 2`)\n\tassert.Contains(t, body, `myapp_requests_total{code=\"502\",host=\"example.com\",method=\"GET\",url=\"/handler_for_error\"} 1`)\n}\n\nfunc TestMiddlewareConfig_LabelFuncs(t *testing.T) {\n\te := echo.New()\n\tcustomRegistry := prometheus.NewRegistry()\n\te.Use(NewMiddlewareWithConfig(MiddlewareConfig{\n\t\tLabelFuncs: map[string]LabelValueFunc{\n\t\t\t\"scheme\": func(c *echo.Context, err error) string { // additional custom label\n\t\t\t\treturn c.Scheme()\n\t\t\t},\n\t\t\t\"method\": func(c *echo.Context, err error) string { // overrides default 'method' label value\n\t\t\t\treturn \"overridden_\" + c.Request().Method\n\t\t\t},\n\t\t},\n\t\tRegisterer: customRegistry,\n\t}))\n\te.GET(\"/metrics\", NewHandlerWithConfig(HandlerConfig{Gatherer: customRegistry}))\n\n\te.GET(\"/ok\", func(c *echo.Context) error {\n\t\treturn c.JSON(http.StatusOK, \"OK\")\n\t})\n\n\tassert.Equal(t, http.StatusOK, request(e, \"/ok\"))\n\n\tbody, code := requestBody(e, \"/metrics\")\n\tassert.Equal(t, http.StatusOK, code)\n\tassert.Contains(t, body, `echo_request_duration_seconds_count{code=\"200\",host=\"example.com\",method=\"overridden_GET\",scheme=\"http\",url=\"/ok\"} 1`)\n}\n\nfunc TestMiddlewareConfig_StatusCodeResolver(t *testing.T) {\n\te := echo.New()\n\tcustomRegistry := prometheus.NewRegistry()\n\tcustomResolver := func(c *echo.Context, err error) int {\n\t\tif err == nil {\n\t\t\tif eResp, uErr := echo.UnwrapResponse(c.Response()); uErr == nil {\n\t\t\t\treturn eResp.Status\n\t\t\t}\n\t\t\treturn http.StatusOK\n\t\t}\n\t\tmsg := err.Error()\n\t\tif strings.Contains(msg, \"NOT FOUND\") {\n\t\t\treturn http.StatusNotFound\n\t\t}\n\t\tif strings.Contains(msg, \"NOT Authorized\") {\n\t\t\treturn http.StatusUnauthorized\n\t\t}\n\t\treturn http.StatusInternalServerError\n\t}\n\te.Use(NewMiddlewareWithConfig(MiddlewareConfig{\n\t\tSkipper: func(c *echo.Context) bool {\n\t\t\treturn strings.HasSuffix(c.Path(), \"ignore\")\n\t\t},\n\t\tSubsystem:          \"myapp\",\n\t\tRegisterer:         customRegistry,\n\t\tStatusCodeResolver: customResolver,\n\t}))\n\te.GET(\"/metrics\", NewHandlerWithConfig(HandlerConfig{Gatherer: customRegistry}))\n\n\te.GET(\"/handler_for_ok\", func(c *echo.Context) error {\n\t\treturn c.JSON(http.StatusOK, \"OK\")\n\t})\n\te.GET(\"/handler_for_nok\", func(c *echo.Context) error {\n\t\treturn c.JSON(http.StatusConflict, \"NOK\")\n\t})\n\te.GET(\"/handler_for_not_found\", func(c *echo.Context) error {\n\t\treturn errors.New(\"NOT FOUND\")\n\t})\n\te.GET(\"/handler_for_not_authorized\", func(c *echo.Context) error {\n\t\treturn errors.New(\"NOT Authorized\")\n\t})\n\te.GET(\"/handler_for_unknown_error\", func(c *echo.Context) error {\n\t\treturn errors.New(\"i do not know\")\n\t})\n\n\tassert.Equal(t, http.StatusOK, request(e, \"/handler_for_ok\"))\n\tassert.Equal(t, http.StatusConflict, request(e, \"/handler_for_nok\"))\n\tassert.Equal(t, http.StatusInternalServerError, request(e, \"/handler_for_not_found\"))\n\tassert.Equal(t, http.StatusInternalServerError, request(e, \"/handler_for_not_authorized\"))\n\tassert.Equal(t, http.StatusInternalServerError, request(e, \"/handler_for_unknown_error\"))\n\n\tbody, code := requestBody(e, \"/metrics\")\n\tassert.Equal(t, http.StatusOK, code)\n\tassert.Contains(t, body, fmt.Sprintf(\"%s_requests_total\", \"myapp\"))\n\tassert.Contains(t, body, `myapp_requests_total{code=\"200\",host=\"example.com\",method=\"GET\",url=\"/handler_for_ok\"} 1`)\n\tassert.Contains(t, body, `myapp_requests_total{code=\"409\",host=\"example.com\",method=\"GET\",url=\"/handler_for_nok\"} 1`)\n\tassert.Contains(t, body, `myapp_requests_total{code=\"404\",host=\"example.com\",method=\"GET\",url=\"/handler_for_not_found\"} 1`)\n\tassert.Contains(t, body, `myapp_requests_total{code=\"401\",host=\"example.com\",method=\"GET\",url=\"/handler_for_not_authorized\"} 1`)\n\tassert.Contains(t, body, `myapp_requests_total{code=\"500\",host=\"example.com\",method=\"GET\",url=\"/handler_for_unknown_error\"} 1`)\n}\n\nfunc TestMiddlewareConfig_HistogramOptsFunc(t *testing.T) {\n\te := echo.New()\n\tcustomRegistry := prometheus.NewRegistry()\n\te.Use(NewMiddlewareWithConfig(MiddlewareConfig{\n\t\tHistogramOptsFunc: func(opts prometheus.HistogramOpts) prometheus.HistogramOpts {\n\t\t\tif opts.Name == \"request_duration_seconds\" {\n\t\t\t\topts.ConstLabels = prometheus.Labels{\"my_const\": \"123\"}\n\t\t\t}\n\t\t\treturn opts\n\t\t},\n\t\tRegisterer: customRegistry,\n\t}))\n\te.GET(\"/metrics\", NewHandlerWithConfig(HandlerConfig{Gatherer: customRegistry}))\n\n\te.GET(\"/ok\", func(c *echo.Context) error {\n\t\treturn c.JSON(http.StatusOK, \"OK\")\n\t})\n\n\tassert.Equal(t, http.StatusOK, request(e, \"/ok\"))\n\n\tbody, code := requestBody(e, \"/metrics\")\n\tassert.Equal(t, http.StatusOK, code)\n\n\t// has const label\n\tassert.Contains(t, body, `echo_request_duration_seconds_count{code=\"200\",host=\"example.com\",method=\"GET\",my_const=\"123\",url=\"/ok\"} 1`)\n\t// does not have const label\n\tassert.Contains(t, body, `echo_request_size_bytes_count{code=\"200\",host=\"example.com\",method=\"GET\",url=\"/ok\"} 1`)\n}\n\nfunc TestMiddlewareConfig_CounterOptsFunc(t *testing.T) {\n\te := echo.New()\n\tcustomRegistry := prometheus.NewRegistry()\n\te.Use(NewMiddlewareWithConfig(MiddlewareConfig{\n\t\tCounterOptsFunc: func(opts prometheus.CounterOpts) prometheus.CounterOpts {\n\t\t\tif opts.Name == \"requests_total\" {\n\t\t\t\topts.ConstLabels = prometheus.Labels{\"my_const\": \"123\"}\n\t\t\t}\n\t\t\treturn opts\n\t\t},\n\t\tRegisterer: customRegistry,\n\t}))\n\te.GET(\"/metrics\", NewHandlerWithConfig(HandlerConfig{Gatherer: customRegistry}))\n\n\te.GET(\"/ok\", func(c *echo.Context) error {\n\t\treturn c.JSON(http.StatusOK, \"OK\")\n\t})\n\n\tassert.Equal(t, http.StatusOK, request(e, \"/ok\"))\n\n\tbody, code := requestBody(e, \"/metrics\")\n\tassert.Equal(t, http.StatusOK, code)\n\n\t// has const label\n\tassert.Contains(t, body, `echo_requests_total{code=\"200\",host=\"example.com\",method=\"GET\",my_const=\"123\",url=\"/ok\"} 1`)\n\t// does not have const label\n\tassert.Contains(t, body, `echo_request_size_bytes_count{code=\"200\",host=\"example.com\",method=\"GET\",url=\"/ok\"} 1`)\n}\n\nfunc TestMiddlewareConfig_AfterNextFuncs(t *testing.T) {\n\te := echo.New()\n\n\tcustomRegistry := prometheus.NewRegistry()\n\tcustomCounter := prometheus.NewCounter(\n\t\tprometheus.CounterOpts{\n\t\t\tName: \"custom_requests_total\",\n\t\t\tHelp: \"How many HTTP requests processed, partitioned by status code and HTTP method.\",\n\t\t},\n\t)\n\tif err := customRegistry.Register(customCounter); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\te.Use(NewMiddlewareWithConfig(MiddlewareConfig{\n\t\tAfterNext: func(c *echo.Context, err error) {\n\t\t\tcustomCounter.Inc() // use our custom metric in middleware\n\t\t},\n\t\tRegisterer: customRegistry,\n\t}))\n\te.GET(\"/metrics\", NewHandlerWithConfig(HandlerConfig{Gatherer: customRegistry}))\n\n\te.GET(\"/ok\", func(c *echo.Context) error {\n\t\treturn c.JSON(http.StatusOK, \"OK\")\n\t})\n\n\tassert.Equal(t, http.StatusOK, request(e, \"/ok\"))\n\n\tbody, code := requestBody(e, \"/metrics\")\n\tassert.Equal(t, http.StatusOK, code)\n\tassert.Contains(t, body, `custom_requests_total 1`)\n}\n\nfunc TestRunPushGatewayGatherer(t *testing.T) {\n\treceivedMetrics := false\n\tsvr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\treceivedMetrics = true\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t\tw.Write([]byte(\"OK\"))\n\t}))\n\tdefer svr.Close()\n\n\tctx, cancel := context.WithTimeout(context.Background(), 250*time.Millisecond)\n\tdefer cancel()\n\n\tconfig := PushGatewayConfig{\n\t\tPushGatewayURL: svr.URL,\n\t\tPushInterval:   10 * time.Millisecond,\n\t\tErrorHandler: func(err error) error {\n\t\t\treturn err // to force return after first request\n\t\t},\n\t}\n\terr := RunPushGatewayGatherer(ctx, config)\n\n\tassert.EqualError(t, err, \"code=400, message=post metrics request did not succeed\")\n\tassert.True(t, receivedMetrics)\n\tunregisterDefaults(\"myapp\")\n}\n\n// TestSetPathFor404NoMatchingRoute tests that the url is not included in the metric when\n// the 404 response is due to no matching route\nfunc TestSetPathFor404NoMatchingRoute(t *testing.T) {\n\te := echo.New()\n\n\te.Use(NewMiddlewareWithConfig(MiddlewareConfig{DoNotUseRequestPathFor404: true, Subsystem: defaultSubsystem}))\n\te.GET(\"/metrics\", NewHandler())\n\n\tassert.Equal(t, http.StatusNotFound, request(e, \"/nonExistentPath\"))\n\n\ts, code := requestBody(e, \"/metrics\")\n\tassert.Equal(t, http.StatusOK, code)\n\tassert.Contains(t, s, fmt.Sprintf(`%s_request_duration_seconds_count{code=\"404\",host=\"example.com\",method=\"GET\",url=\"\"} 1`, defaultSubsystem))\n\tassert.NotContains(t, s, fmt.Sprintf(`%s_request_duration_seconds_count{code=\"404\",host=\"example.com\",method=\"GET\",url=\"/nonExistentPath\"} 1`, defaultSubsystem))\n\n\tunregisterDefaults(defaultSubsystem)\n}\n\n// TestSetPathFor404Logic tests that the url is included in the metric when the 404 response is due to logic\nfunc TestSetPathFor404Logic(t *testing.T) {\n\tunregisterDefaults(\"myapp\")\n\te := echo.New()\n\n\te.Use(NewMiddlewareWithConfig(MiddlewareConfig{DoNotUseRequestPathFor404: true, Subsystem: defaultSubsystem}))\n\te.GET(\"/metrics\", NewHandler())\n\n\te.GET(\"/sample\", func(c *echo.Context) error {\n\t\treturn echo.ErrNotFound\n\t})\n\n\tassert.Equal(t, http.StatusNotFound, request(e, \"/sample\"))\n\n\ts, code := requestBody(e, \"/metrics\")\n\tassert.Equal(t, http.StatusOK, code)\n\tassert.NotContains(t, s, fmt.Sprintf(`%s_request_duration_seconds_count{code=\"404\",host=\"example.com\",method=\"GET\",url=\"\"} 1`, defaultSubsystem))\n\tassert.Contains(t, s, fmt.Sprintf(`%s_request_duration_seconds_count{code=\"404\",host=\"example.com\",method=\"GET\",url=\"/sample\"} 1`, defaultSubsystem))\n\n\tunregisterDefaults(defaultSubsystem)\n}\n\nfunc TestInvalidUTF8PathIsFixed(t *testing.T) {\n\te := echo.New()\n\n\te.Use(NewMiddlewareWithConfig(MiddlewareConfig{Subsystem: defaultSubsystem}))\n\te.GET(\"/metrics\", NewHandler())\n\n\tassert.Equal(t, http.StatusNotFound, request(e, \"/../../WEB-INF/web.xml\\xc0\\x80.jsp\"))\n\n\ts, code := requestBody(e, \"/metrics\")\n\tassert.Equal(t, http.StatusOK, code)\n\tassert.Contains(t, s, fmt.Sprintf(`%s_request_duration_seconds_count{code=\"404\",host=\"example.com\",method=\"GET\",url=\"/../../WEB-INF/web.xml�.jsp\"} 1`, defaultSubsystem))\n\n\tunregisterDefaults(defaultSubsystem)\n}\n\nfunc requestBody(e *echo.Echo, path string) (string, int) {\n\treq := httptest.NewRequest(http.MethodGet, path, nil)\n\trec := httptest.NewRecorder()\n\te.ServeHTTP(rec, req)\n\n\treturn rec.Body.String(), rec.Code\n}\n\nfunc request(e *echo.Echo, path string) int {\n\t_, code := requestBody(e, path)\n\treturn code\n}\n\nfunc unregisterDefaults(subsystem string) {\n\t// this is extremely hacky way to unregister our middleware metrics that it registers to prometheus default registry\n\t// Metrics/collector can be unregistered only by their instance but we do not have their instance, so we need to\n\t// create similar collector to register it and get error back with that existing collector we actually want to\n\t// unregister\n\tp := prometheus.DefaultRegisterer\n\n\tunRegisterCollector := func(opts prometheus.Opts) {\n\t\tdummyDuplicate := prometheus.NewCounterVec(prometheus.CounterOpts(opts), []string{\"code\", \"method\", \"host\", \"url\"})\n\t\terr := p.Register(dummyDuplicate)\n\t\tif err == nil {\n\t\t\treturn\n\t\t}\n\t\tvar arErr prometheus.AlreadyRegisteredError\n\t\tif errors.As(err, &arErr) {\n\t\t\tp.Unregister(arErr.ExistingCollector)\n\t\t}\n\t}\n\n\tunRegisterCollector(prometheus.Opts{\n\t\tSubsystem: subsystem,\n\t\tName:      \"requests_total\",\n\t\tHelp:      \"How many HTTP requests processed, partitioned by status code and HTTP method.\",\n\t})\n\tunRegisterCollector(prometheus.Opts{\n\t\tSubsystem: subsystem,\n\t\tName:      \"request_duration_seconds\",\n\t\tHelp:      \"The HTTP request latencies in seconds.\",\n\t})\n\tunRegisterCollector(prometheus.Opts{\n\t\tSubsystem: subsystem,\n\t\tName:      \"response_size_bytes\",\n\t\tHelp:      \"The HTTP response sizes in bytes.\",\n\t})\n\tunRegisterCollector(prometheus.Opts{\n\t\tSubsystem: subsystem,\n\t\tName:      \"request_size_bytes\",\n\t\tHelp:      \"The HTTP request sizes in bytes.\",\n\t})\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/labstack/echo-contrib/v5\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/casbin/casbin/v2 v2.135.0\n\tgithub.com/gorilla/sessions v1.4.0\n\tgithub.com/labstack/echo/v5 v5.0.4\n\tgithub.com/opentracing/opentracing-go v1.2.0\n\tgithub.com/openzipkin/zipkin-go v0.4.3\n\tgithub.com/prometheus/client_golang v1.23.2\n\tgithub.com/prometheus/common v0.67.5\n\tgithub.com/stretchr/testify v1.11.1\n\tgithub.com/uber/jaeger-client-go v2.30.0+incompatible\n)\n\nrequire (\n\tgithub.com/HdrHistogram/hdrhistogram-go v1.2.0 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/bmatcuk/doublestar/v4 v4.10.0 // indirect\n\tgithub.com/casbin/govaluate v1.10.0 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/gorilla/securecookie v1.1.2 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/procfs v0.20.1 // indirect\n\tgithub.com/uber/jaeger-lib v2.4.1+incompatible // indirect\n\tgo.uber.org/atomic v1.11.0 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.4 // indirect\n\tgolang.org/x/sys v0.42.0 // indirect\n\tgolang.org/x/time v0.15.0 // indirect\n\tgoogle.golang.org/grpc v1.79.3 // indirect\n\tgoogle.golang.org/protobuf v1.36.11 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/HdrHistogram/hdrhistogram-go v1.2.0 h1:XMJkDWuz6bM9Fzy7zORuVFKH7ZJY41G2q8KWhVGkNiY=\ngithub.com/HdrHistogram/hdrhistogram-go v1.2.0/go.mod h1:CiIeGiHSd06zjX+FypuEJ5EQ07KKtxZ+8J6hszwVQig=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=\ngithub.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=\ngithub.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=\ngithub.com/casbin/casbin/v2 v2.135.0 h1:6BLkMQiGotYyS5yYeWgW19vxqugUlvHFkFiLnLR/bxk=\ngithub.com/casbin/casbin/v2 v2.135.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=\ngithub.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=\ngithub.com/casbin/govaluate v1.10.0 h1:ffGw51/hYH3w3rZcxO/KcaUIDOLP84w7nsidMVgaDG0=\ngithub.com/casbin/govaluate v1.10.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=\ngithub.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=\ngithub.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=\ngithub.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=\ngithub.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=\ngithub.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=\ngithub.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/labstack/echo/v5 v5.0.4 h1:ll3I/O8BifjMztj9dD1vx/peZQv8cR2CTUdQK6QxGGc=\ngithub.com/labstack/echo/v5 v5.0.4/go.mod h1:SyvlSdObGjRXeQfCCXW/sybkZdOOQZBmpKF0bvALaeo=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=\ngithub.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=\ngithub.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=\ngithub.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=\ngithub.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=\ngithub.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=\ngithub.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=\ngithub.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=\ngithub.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=\ngithub.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=\ngithub.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=\ngo.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=\ngo.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=\ngo.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=\ngolang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=\ngolang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=\ngolang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=\ngolang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=\ngolang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngoogle.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=\ngoogle.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "internal/helpers/statuscode.go",
    "content": "package helpers\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\n\t\"github.com/labstack/echo/v5\"\n)\n\n// DefaultStatusResolver resolves http status code from given err or Response.\nfunc DefaultStatusResolver(c *echo.Context, err error) int {\n\tstatus := 0\n\tvar sc echo.HTTPStatusCoder\n\tif errors.As(err, &sc) {\n\t\treturn sc.StatusCode()\n\t}\n\tif eResp, uErr := echo.UnwrapResponse(c.Response()); uErr == nil {\n\t\tif eResp.Committed {\n\t\t\tstatus = eResp.Status\n\t\t}\n\t}\n\tif err != nil && status == 0 {\n\t\tstatus = http.StatusInternalServerError\n\t}\n\treturn status\n}\n"
  },
  {
    "path": "jaegertracing/jaegertracing.go",
    "content": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\n/*\nPackage jaegertracing provides middleware to Opentracing using Jaeger.\n\nExample:\n```\npackage main\nimport (\n\n\t\"github.com/labstack/echo-contrib/v5/jaegertracing\"\n\t\"github.com/labstack/echo/v5\"\n\n)\n\n\tfunc main() {\n\t    e := echo.New()\n\t    // Enable tracing middleware\n\t    c := jaegertracing.New(e, nil)\n\t    defer c.Close()\n\n\t    e.Logger.Fatal(e.Start(\":1323\"))\n\t}\n\n```\n\nDeprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/jaegertracing/jaeger-client-go\n*/\npackage jaegertracing\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/labstack/echo-contrib/v5/internal/helpers\"\n\t\"github.com/labstack/echo/v5\"\n\t\"github.com/labstack/echo/v5/middleware\"\n\t\"github.com/opentracing/opentracing-go\"\n\t\"github.com/opentracing/opentracing-go/ext\"\n\t\"github.com/uber/jaeger-client-go/config\"\n)\n\nconst defaultComponentName = \"echo/v5\"\n\ntype (\n\t// TraceConfig defines the config for Trace middleware.\n\tTraceConfig struct {\n\t\t// Skipper defines a function to skip middleware.\n\t\tSkipper middleware.Skipper\n\n\t\t// OpenTracing Tracer instance which should be got before\n\t\tTracer opentracing.Tracer\n\n\t\t// ComponentName used for describing the tracing component name\n\t\tComponentName string\n\n\t\t// add req body & resp body to tracing tags\n\t\tIsBodyDump bool\n\n\t\t// prevent logging long http request bodies\n\t\tLimitHTTPBody bool\n\n\t\t// http body limit size (in bytes)\n\t\t// NOTE: don't specify values larger than 60000 as jaeger can't handle values in span.LogKV larger than 60000 bytes\n\t\tLimitSize int\n\n\t\t// OperationNameFunc composes operation name based on context. Can be used to override default naming\n\t\tOperationNameFunc func(c *echo.Context) string\n\t}\n)\n\nvar (\n\t// DefaultTraceConfig is the default Trace middleware config.\n\tDefaultTraceConfig = TraceConfig{\n\t\tSkipper:       middleware.DefaultSkipper,\n\t\tComponentName: defaultComponentName,\n\t\tIsBodyDump:    false,\n\n\t\tLimitHTTPBody:     true,\n\t\tLimitSize:         60_000,\n\t\tOperationNameFunc: defaultOperationName,\n\t}\n)\n\n// New creates an Opentracing tracer and attaches it to Echo middleware.\n// Returns Closer do be added to caller function as `defer closer.Close()`\n//\n// Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/jaegertracing/jaeger-client-go\nfunc New(e *echo.Echo, skipper middleware.Skipper) io.Closer {\n\t// Add Opentracing instrumentation\n\tdefcfg := config.Configuration{\n\t\tServiceName: \"echo-tracer\",\n\t\tSampler: &config.SamplerConfig{\n\t\t\tType:  \"const\",\n\t\t\tParam: 1,\n\t\t},\n\t\tReporter: &config.ReporterConfig{\n\t\t\tLogSpans:            true,\n\t\t\tBufferFlushInterval: 1 * time.Second,\n\t\t},\n\t}\n\tcfg, err := defcfg.FromEnv()\n\tif err != nil {\n\t\tpanic(\"Could not parse Jaeger env vars: \" + err.Error())\n\t}\n\ttracer, closer, err := cfg.NewTracer()\n\tif err != nil {\n\t\tpanic(\"Could not initialize jaeger tracer: \" + err.Error())\n\t}\n\n\topentracing.SetGlobalTracer(tracer)\n\te.Use(TraceWithConfig(TraceConfig{\n\t\tTracer:  tracer,\n\t\tSkipper: skipper,\n\t}))\n\treturn closer\n}\n\n// Trace returns a Trace middleware.\n// Trace middleware traces http requests and reporting errors.\n//\n// Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/jaegertracing/jaeger-client-go\nfunc Trace(tracer opentracing.Tracer) echo.MiddlewareFunc {\n\tc := DefaultTraceConfig\n\tc.Tracer = tracer\n\tc.ComponentName = defaultComponentName\n\treturn TraceWithConfig(c)\n}\n\n// TraceWithConfig returns a Trace middleware with config.\n// See: `Trace()`.\n//\n// Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/jaegertracing/jaeger-client-go\nfunc TraceWithConfig(config TraceConfig) echo.MiddlewareFunc {\n\tif config.Tracer == nil {\n\t\tpanic(\"echo: trace middleware requires opentracing tracer\")\n\t}\n\tif config.Skipper == nil {\n\t\tconfig.Skipper = middleware.DefaultSkipper\n\t}\n\tif config.ComponentName == \"\" {\n\t\tconfig.ComponentName = defaultComponentName\n\t}\n\tif config.OperationNameFunc == nil {\n\t\tconfig.OperationNameFunc = defaultOperationName\n\t}\n\n\treturn func(next echo.HandlerFunc) echo.HandlerFunc {\n\t\treturn func(c *echo.Context) error {\n\t\t\tif config.Skipper(c) {\n\t\t\t\treturn next(c)\n\t\t\t}\n\n\t\t\treq := c.Request()\n\t\t\topname := config.OperationNameFunc(c)\n\t\t\trealIP := c.RealIP()\n\t\t\trequestID := getRequestID(c) // request-id generated by reverse-proxy\n\n\t\t\tvar sp opentracing.Span\n\t\t\tvar err error\n\n\t\t\tctx, err := config.Tracer.Extract(\n\t\t\t\topentracing.HTTPHeaders,\n\t\t\t\topentracing.HTTPHeadersCarrier(req.Header),\n\t\t\t)\n\n\t\t\tif err != nil {\n\t\t\t\tsp = config.Tracer.StartSpan(opname)\n\t\t\t} else {\n\t\t\t\tsp = config.Tracer.StartSpan(opname, ext.RPCServerOption(ctx))\n\t\t\t}\n\t\t\tdefer sp.Finish()\n\n\t\t\text.HTTPMethod.Set(sp, req.Method)\n\t\t\text.HTTPUrl.Set(sp, req.URL.String())\n\t\t\text.Component.Set(sp, config.ComponentName)\n\t\t\tsp.SetTag(\"client_ip\", realIP)\n\t\t\tsp.SetTag(\"request_id\", requestID)\n\n\t\t\t// Dump request & response body\n\t\t\tvar respDumper *responseDumper\n\t\t\tif config.IsBodyDump {\n\t\t\t\t// request\n\t\t\t\treqBody := []byte{}\n\t\t\t\tif c.Request().Body != nil {\n\t\t\t\t\treqBody, _ = io.ReadAll(c.Request().Body)\n\n\t\t\t\t\tif config.LimitHTTPBody {\n\t\t\t\t\t\tsp.LogKV(\"http.req.body\", limitString(string(reqBody), config.LimitSize))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsp.LogKV(\"http.req.body\", string(reqBody))\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treq.Body = io.NopCloser(bytes.NewBuffer(reqBody)) // reset original request body\n\n\t\t\t\t// response\n\t\t\t\trespDumper = newResponseDumper(c.Response())\n\t\t\t\tc.SetResponse(respDumper)\n\t\t\t}\n\n\t\t\t// setup request context - add opentracing span\n\t\t\treqSpan := req.WithContext(opentracing.ContextWithSpan(req.Context(), sp))\n\t\t\tc.SetRequest(reqSpan)\n\t\t\tdefer func() {\n\t\t\t\t// as we have created new http.Request object we need to make sure that temporary files created to hold MultipartForm\n\t\t\t\t// files are cleaned up. This is done by http.Server at the end of request lifecycle but Server does not\n\t\t\t\t// have reference to our new Request instance therefore it is our responsibility to fix the mess we caused.\n\t\t\t\t//\n\t\t\t\t// This means that when we are on returning path from handler middlewares up in chain from this middleware\n\t\t\t\t// can not access these temporary files anymore because we deleted them here.\n\t\t\t\tif reqSpan.MultipartForm != nil {\n\t\t\t\t\treqSpan.MultipartForm.RemoveAll()\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t// inject Jaeger context into request header\n\t\t\tconfig.Tracer.Inject(sp.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(c.Request().Header))\n\n\t\t\t// call next middleware / controller\n\t\t\terr = next(c)\n\n\t\t\tstatus := helpers.DefaultStatusResolver(c, err)\n\t\t\text.HTTPStatusCode.Set(sp, uint16(status))\n\n\t\t\tif err != nil {\n\t\t\t\tlogError(sp, err)\n\t\t\t}\n\n\t\t\t// Dump response body\n\t\t\tif config.IsBodyDump {\n\t\t\t\tif config.LimitHTTPBody {\n\t\t\t\t\tsp.LogKV(\"http.resp.body\", limitString(respDumper.GetResponse(), config.LimitSize))\n\t\t\t\t} else {\n\t\t\t\t\tsp.LogKV(\"http.resp.body\", respDumper.GetResponse())\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn nil // error was already processed with ctx.Error(err)\n\t\t}\n\t}\n}\n\nfunc limitString(str string, size int) string {\n\tif len(str) > size {\n\t\treturn str[:size/2] + \"\\n---- skipped ----\\n\" + str[len(str)-size/2:]\n\t}\n\n\treturn str\n}\n\nfunc logError(span opentracing.Span, err error) {\n\tvar httpError *echo.HTTPError\n\tif errors.As(err, &httpError) {\n\t\tspan.LogKV(\"error.message\", httpError.Message)\n\t} else {\n\t\tspan.LogKV(\"error.message\", err.Error())\n\t}\n\tspan.SetTag(\"error\", true)\n}\n\nfunc getRequestID(ctx *echo.Context) string {\n\trequestID := ctx.Request().Header.Get(echo.HeaderXRequestID) // request-id generated by reverse-proxy\n\tif requestID == \"\" {\n\t\trequestID = generateToken() // missed request-id from proxy, we generate it manually\n\t}\n\treturn requestID\n}\n\nfunc generateToken() string {\n\tb := make([]byte, 16)\n\trand.Read(b)\n\treturn fmt.Sprintf(\"%x\", b)\n}\n\nfunc defaultOperationName(c *echo.Context) string {\n\treq := c.Request()\n\treturn \"HTTP \" + req.Method + \" URL: \" + c.Path()\n}\n\n// TraceFunction wraps funtion with opentracing span adding tags for the function name and caller details\n//\n// Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/jaegertracing/jaeger-client-go\nfunc TraceFunction(ctx *echo.Context, fn interface{}, params ...interface{}) (result []reflect.Value) {\n\t// Get function name\n\tname := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()\n\t// Create child span\n\tparentSpan := opentracing.SpanFromContext(ctx.Request().Context())\n\tsp := opentracing.StartSpan(\n\t\t\"Function - \"+name,\n\t\topentracing.ChildOf(parentSpan.Context()))\n\tdefer sp.Finish()\n\n\tsp.SetTag(\"function\", name)\n\n\t// Get caller function name, file and line\n\tpc := make([]uintptr, 15)\n\tn := runtime.Callers(2, pc)\n\tframes := runtime.CallersFrames(pc[:n])\n\tframe, _ := frames.Next()\n\tcallerDetails := fmt.Sprintf(\"%s - %s#%d\", frame.Function, frame.File, frame.Line)\n\tsp.SetTag(\"caller\", callerDetails)\n\n\t// Check params and call function\n\tf := reflect.ValueOf(fn)\n\tif f.Type().NumIn() != len(params) {\n\t\te := fmt.Sprintf(\"Incorrect number of parameters calling wrapped function %s\", name)\n\t\tpanic(e)\n\t}\n\tinputs := make([]reflect.Value, len(params))\n\tfor k, in := range params {\n\t\tinputs[k] = reflect.ValueOf(in)\n\t}\n\treturn f.Call(inputs)\n}\n\n// CreateChildSpan creates a new opentracing span adding tags for the span name and caller details.\n// User must call defer `sp.Finish()`\n//\n// Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/jaegertracing/jaeger-client-go\nfunc CreateChildSpan(ctx *echo.Context, name string) opentracing.Span {\n\tparentSpan := opentracing.SpanFromContext(ctx.Request().Context())\n\tsp := opentracing.StartSpan(\n\t\tname,\n\t\topentracing.ChildOf(parentSpan.Context()))\n\tsp.SetTag(\"name\", name)\n\n\t// Get caller function name, file and line\n\tpc := make([]uintptr, 15)\n\tn := runtime.Callers(2, pc)\n\tframes := runtime.CallersFrames(pc[:n])\n\tframe, _ := frames.Next()\n\tcallerDetails := fmt.Sprintf(\"%s - %s#%d\", frame.Function, frame.File, frame.Line)\n\tsp.SetTag(\"caller\", callerDetails)\n\n\treturn sp\n}\n\n// NewTracedRequest generates a new traced HTTP request with opentracing headers injected into it\n//\n// Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/jaegertracing/jaeger-client-go\nfunc NewTracedRequest(method string, url string, body io.Reader, span opentracing.Span) (*http.Request, error) {\n\treq, err := http.NewRequest(method, url, body)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\n\text.SpanKindRPCClient.Set(span)\n\text.HTTPUrl.Set(span, url)\n\text.HTTPMethod.Set(span, method)\n\tspan.Tracer().Inject(span.Context(),\n\t\topentracing.HTTPHeaders,\n\t\topentracing.HTTPHeadersCarrier(req.Header))\n\n\treturn req, err\n}\n"
  },
  {
    "path": "jaegertracing/jaegertracing_test.go",
    "content": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage jaegertracing\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/labstack/echo/v5\"\n\t\"github.com/opentracing/opentracing-go\"\n\t\"github.com/opentracing/opentracing-go/log\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// Mock opentracing.Span\ntype mockSpan struct {\n\ttracer   opentracing.Tracer\n\ttags     map[string]interface{}\n\tlogs     map[string]interface{}\n\topName   string\n\tfinished bool\n}\n\nfunc createSpan(tracer opentracing.Tracer) *mockSpan {\n\treturn &mockSpan{\n\t\ttracer: tracer,\n\t\ttags:   make(map[string]interface{}),\n\t\tlogs:   make(map[string]interface{}),\n\t}\n}\n\nfunc (sp *mockSpan) isFinished() bool {\n\treturn sp.finished\n}\n\nfunc (sp *mockSpan) getOpName() string {\n\treturn sp.opName\n}\n\nfunc (sp *mockSpan) getTag(key string) interface{} {\n\treturn sp.tags[key]\n}\n\nfunc (sp *mockSpan) getLog(key string) interface{} {\n\treturn sp.logs[key]\n}\n\nfunc (sp *mockSpan) Finish() {\n\tsp.finished = true\n}\nfunc (sp *mockSpan) FinishWithOptions(opts opentracing.FinishOptions) {\n}\nfunc (sp *mockSpan) Context() opentracing.SpanContext {\n\treturn nil\n}\nfunc (sp *mockSpan) SetOperationName(operationName string) opentracing.Span {\n\tsp.opName = operationName\n\treturn sp\n}\nfunc (sp *mockSpan) SetTag(key string, value interface{}) opentracing.Span {\n\tsp.tags[key] = value\n\treturn sp\n}\nfunc (sp *mockSpan) LogFields(fields ...log.Field) {\n}\nfunc (sp *mockSpan) LogKV(alternatingKeyValues ...interface{}) {\n\tfor i := 0; i < len(alternatingKeyValues); i += 2 {\n\t\tikey := alternatingKeyValues[i]\n\t\tvalue := alternatingKeyValues[i+1]\n\t\tif key, ok := ikey.(string); ok {\n\t\t\tsp.logs[key] = value\n\t\t}\n\t}\n}\nfunc (sp *mockSpan) SetBaggageItem(restrictedKey, value string) opentracing.Span {\n\treturn sp\n}\nfunc (sp *mockSpan) BaggageItem(restrictedKey string) string {\n\treturn \"\"\n}\nfunc (sp *mockSpan) Tracer() opentracing.Tracer {\n\treturn sp.tracer\n}\nfunc (sp *mockSpan) LogEvent(event string) {\n}\nfunc (sp *mockSpan) LogEventWithPayload(event string, payload interface{}) {\n}\nfunc (sp *mockSpan) Log(data opentracing.LogData) {\n}\n\n// Mock opentracing.Tracer\ntype mockTracer struct {\n\tspan                   *mockSpan\n\thasStartSpanWithOption bool\n}\n\nfunc (tr *mockTracer) currentSpan() *mockSpan {\n\treturn tr.span\n}\n\nfunc (tr *mockTracer) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span {\n\ttr.hasStartSpanWithOption = len(opts) > 0\n\tif tr.span != nil {\n\t\ttr.span.opName = operationName\n\t\treturn tr.span\n\t}\n\tspan := createSpan(tr)\n\tspan.opName = operationName\n\treturn span\n}\n\nfunc (tr *mockTracer) Inject(sm opentracing.SpanContext, format interface{}, carrier interface{}) error {\n\treturn nil\n}\n\nfunc (tr *mockTracer) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) {\n\tif tr.span != nil {\n\t\treturn nil, nil\n\t}\n\treturn nil, errors.New(\"no span\")\n}\n\nfunc createMockTracer() *mockTracer {\n\ttracer := mockTracer{}\n\tspan := createSpan(&tracer)\n\ttracer.span = span\n\treturn &tracer\n}\n\nfunc TestTraceWithDefaultConfig(t *testing.T) {\n\ttracer := createMockTracer()\n\n\te := echo.New()\n\te.Use(Trace(tracer))\n\n\te.GET(\"/hello\", func(c *echo.Context) error {\n\t\treturn c.String(http.StatusOK, \"world\")\n\t})\n\n\te.GET(\"/giveme400\", func(c *echo.Context) error {\n\t\treturn echo.NewHTTPError(http.StatusBadRequest, \"baaaad request\")\n\t})\n\n\te.GET(\"/givemeerror\", func(c *echo.Context) error {\n\t\treturn fmt.Errorf(\"internal stuff went wrong\")\n\t})\n\n\tt.Run(\"successful call\", func(t *testing.T) {\n\t\treq := httptest.NewRequest(http.MethodGet, \"/hello\", nil)\n\t\trec := httptest.NewRecorder()\n\t\te.ServeHTTP(rec, req)\n\n\t\tassert.Equal(t, \"GET\", tracer.currentSpan().getTag(\"http.method\"))\n\t\tassert.Equal(t, \"/hello\", tracer.currentSpan().getTag(\"http.url\"))\n\t\tassert.Equal(t, defaultComponentName, tracer.currentSpan().getTag(\"component\"))\n\t\tassert.Equal(t, uint16(200), tracer.currentSpan().getTag(\"http.status_code\"))\n\t\tassert.NotEqual(t, true, tracer.currentSpan().getTag(\"error\"))\n\t})\n\n\tt.Run(\"error from echo\", func(t *testing.T) {\n\t\treq := httptest.NewRequest(http.MethodGet, \"/idontexist\", nil)\n\t\trec := httptest.NewRecorder()\n\t\te.ServeHTTP(rec, req)\n\n\t\tassert.Equal(t, \"GET\", tracer.currentSpan().getTag(\"http.method\"))\n\t\tassert.Equal(t, \"/idontexist\", tracer.currentSpan().getTag(\"http.url\"))\n\t\tassert.Equal(t, defaultComponentName, tracer.currentSpan().getTag(\"component\"))\n\t\tassert.Equal(t, uint16(404), tracer.currentSpan().getTag(\"http.status_code\"))\n\t\tassert.Equal(t, true, tracer.currentSpan().getTag(\"error\"))\n\t})\n\n\tt.Run(\"custom http error\", func(t *testing.T) {\n\t\treq := httptest.NewRequest(http.MethodGet, \"/giveme400\", nil)\n\t\trec := httptest.NewRecorder()\n\t\te.ServeHTTP(rec, req)\n\n\t\tassert.Equal(t, uint16(400), tracer.currentSpan().getTag(\"http.status_code\"))\n\t\tassert.Equal(t, true, tracer.currentSpan().getTag(\"error\"))\n\t\tassert.Equal(t, \"baaaad request\", tracer.currentSpan().getLog(\"error.message\"))\n\t})\n\n\tt.Run(\"unknown error\", func(t *testing.T) {\n\t\treq := httptest.NewRequest(http.MethodGet, \"/givemeerror\", nil)\n\t\trec := httptest.NewRecorder()\n\t\te.ServeHTTP(rec, req)\n\n\t\tassert.Equal(t, uint16(500), tracer.currentSpan().getTag(\"http.status_code\"))\n\t\tassert.Equal(t, true, tracer.currentSpan().getTag(\"error\"))\n\t\tassert.Equal(t, \"internal stuff went wrong\", tracer.currentSpan().getLog(\"error.message\"))\n\t})\n}\n\nfunc TestTraceWithConfig(t *testing.T) {\n\ttracer := createMockTracer()\n\n\te := echo.New()\n\te.Use(TraceWithConfig(TraceConfig{\n\t\tTracer:        tracer,\n\t\tComponentName: \"EchoTracer\",\n\t}))\n\treq := httptest.NewRequest(http.MethodGet, \"/trace\", nil)\n\trec := httptest.NewRecorder()\n\te.ServeHTTP(rec, req)\n\n\tassert.Equal(t, true, tracer.currentSpan().isFinished())\n\tassert.Equal(t, \"/trace\", tracer.currentSpan().getTag(\"http.url\"))\n\tassert.Equal(t, \"EchoTracer\", tracer.currentSpan().getTag(\"component\"))\n\tassert.Equal(t, true, tracer.hasStartSpanWithOption)\n\n}\n\nfunc TestTraceWithConfigOfBodyDump(t *testing.T) {\n\ttracer := createMockTracer()\n\n\te := echo.New()\n\te.Use(TraceWithConfig(TraceConfig{\n\t\tTracer:        tracer,\n\t\tComponentName: \"EchoTracer\",\n\t\tIsBodyDump:    true,\n\t}))\n\te.POST(\"/trace\", func(c *echo.Context) error {\n\t\treturn c.String(200, \"Hi\")\n\t})\n\n\treq := httptest.NewRequest(http.MethodPost, \"/trace\", bytes.NewBufferString(`{\"name\": \"Lorem\"}`))\n\trec := httptest.NewRecorder()\n\te.ServeHTTP(rec, req)\n\n\tassert.Equal(t, true, tracer.currentSpan().isFinished())\n\tassert.Equal(t, \"EchoTracer\", tracer.currentSpan().getTag(\"component\"))\n\tassert.Equal(t, \"/trace\", tracer.currentSpan().getTag(\"http.url\"))\n\tassert.Equal(t, `{\"name\": \"Lorem\"}`, tracer.currentSpan().getLog(\"http.req.body\"))\n\tassert.Equal(t, `Hi`, tracer.currentSpan().getLog(\"http.resp.body\"))\n\tassert.Equal(t, uint16(200), tracer.currentSpan().getTag(\"http.status_code\"))\n\tassert.Equal(t, nil, tracer.currentSpan().getTag(\"error\"))\n\tassert.Equal(t, true, tracer.hasStartSpanWithOption)\n\n}\n\nfunc TestTraceWithConfigOfNoneComponentName(t *testing.T) {\n\ttracer := createMockTracer()\n\n\te := echo.New()\n\te.Use(TraceWithConfig(TraceConfig{\n\t\tTracer: tracer,\n\t}))\n\treq := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\trec := httptest.NewRecorder()\n\te.ServeHTTP(rec, req)\n\n\tassert.Equal(t, true, tracer.currentSpan().isFinished())\n\tassert.Equal(t, defaultComponentName, tracer.currentSpan().getTag(\"component\"))\n}\n\nfunc TestTraceWithConfigOfSkip(t *testing.T) {\n\ttracer := createMockTracer()\n\te := echo.New()\n\te.Use(TraceWithConfig(TraceConfig{\n\t\tSkipper: func(*echo.Context) bool {\n\t\t\treturn true\n\t\t},\n\t\tTracer: tracer,\n\t}))\n\treq := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\trec := httptest.NewRecorder()\n\te.ServeHTTP(rec, req)\n\n\tassert.Equal(t, false, tracer.currentSpan().isFinished())\n}\n\nfunc TestTraceOfNoCurrentSpan(t *testing.T) {\n\ttracer := &mockTracer{}\n\te := echo.New()\n\te.Use(Trace(tracer))\n\treq := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\trec := httptest.NewRecorder()\n\te.ServeHTTP(rec, req)\n\n\tassert.Equal(t, false, tracer.hasStartSpanWithOption)\n}\n\nfunc TestTraceWithLimitHTTPBody(t *testing.T) {\n\ttracer := createMockTracer()\n\n\te := echo.New()\n\te.Use(TraceWithConfig(TraceConfig{\n\t\tTracer:        tracer,\n\t\tComponentName: \"EchoTracer\",\n\t\tIsBodyDump:    true,\n\t\tLimitHTTPBody: true,\n\t\tLimitSize:     10,\n\t}))\n\te.POST(\"/trace\", func(c *echo.Context) error {\n\t\treturn c.String(200, \"Hi 123456789012345678901234567890\")\n\t})\n\n\treq := httptest.NewRequest(http.MethodPost, \"/trace\", bytes.NewBufferString(\"123456789012345678901234567890\"))\n\trec := httptest.NewRecorder()\n\te.ServeHTTP(rec, req)\n\n\tassert.Equal(t, true, tracer.currentSpan().isFinished())\n\tassert.Equal(t, \"12345\\n---- skipped ----\\n67890\", tracer.currentSpan().getLog(\"http.req.body\"))\n\tassert.Equal(t, \"Hi 12\\n---- skipped ----\\n67890\", tracer.currentSpan().getLog(\"http.resp.body\"))\n}\n\nfunc TestTraceWithoutLimitHTTPBody(t *testing.T) {\n\ttracer := createMockTracer()\n\n\te := echo.New()\n\te.Use(TraceWithConfig(TraceConfig{\n\t\tTracer:        tracer,\n\t\tComponentName: \"EchoTracer\",\n\t\tIsBodyDump:    true,\n\t\tLimitHTTPBody: false, // disabled\n\t\tLimitSize:     10,\n\t}))\n\te.POST(\"/trace\", func(c *echo.Context) error {\n\t\treturn c.String(200, \"Hi 123456789012345678901234567890\")\n\t})\n\n\treq := httptest.NewRequest(http.MethodPost, \"/trace\", bytes.NewBufferString(\"123456789012345678901234567890\"))\n\trec := httptest.NewRecorder()\n\te.ServeHTTP(rec, req)\n\n\tassert.Equal(t, true, tracer.currentSpan().isFinished())\n\tassert.Equal(t, \"123456789012345678901234567890\", tracer.currentSpan().getLog(\"http.req.body\"))\n\tassert.Equal(t, \"Hi 123456789012345678901234567890\", tracer.currentSpan().getLog(\"http.resp.body\"))\n}\n\nfunc TestTraceWithDefaultOperationName(t *testing.T) {\n\ttracer := createMockTracer()\n\n\te := echo.New()\n\te.Use(Trace(tracer))\n\n\te.GET(\"/trace\", func(c *echo.Context) error {\n\t\treturn c.String(http.StatusOK, \"Hi\")\n\t})\n\n\treq := httptest.NewRequest(http.MethodGet, \"/trace\", nil)\n\trec := httptest.NewRecorder()\n\te.ServeHTTP(rec, req)\n\n\tassert.Equal(t, \"HTTP GET URL: /trace\", tracer.currentSpan().getOpName())\n}\n\nfunc TestTraceWithCustomOperationName(t *testing.T) {\n\ttracer := createMockTracer()\n\n\te := echo.New()\n\te.Use(TraceWithConfig(TraceConfig{\n\t\tTracer:        tracer,\n\t\tComponentName: \"EchoTracer\",\n\t\tOperationNameFunc: func(c *echo.Context) string {\n\t\t\t// This is an example of operation name customization\n\t\t\t// In most cases default formatting is more than enough\n\t\t\treq := c.Request()\n\t\t\topName := \"HTTP \" + req.Method\n\n\t\t\tpath := c.Path()\n\t\t\tfor _, pv := range c.PathValues() {\n\t\t\t\tfrom := \":\" + pv.Name\n\t\t\t\tto := \"{\" + pv.Name + \"}\"\n\t\t\t\tpath = strings.ReplaceAll(path, from, to)\n\t\t\t}\n\n\t\t\treturn opName + \" \" + path\n\t\t},\n\t}))\n\n\te.GET(\"/trace/:traceID/spans/:spanID\", func(c *echo.Context) error {\n\t\treturn c.String(http.StatusOK, \"Hi\")\n\t})\n\n\treq := httptest.NewRequest(http.MethodGet, \"/trace/123456/spans/123\", nil)\n\trec := httptest.NewRecorder()\n\te.ServeHTTP(rec, req)\n\n\tassert.Equal(t, true, tracer.currentSpan().isFinished())\n\tassert.Equal(t, \"HTTP GET /trace/{traceID}/spans/{spanID}\", tracer.currentSpan().getOpName())\n}\n"
  },
  {
    "path": "jaegertracing/response_dumper.go",
    "content": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage jaegertracing\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n)\n\ntype responseDumper struct {\n\thttp.ResponseWriter\n\n\tmw  io.Writer\n\tbuf *bytes.Buffer\n}\n\nfunc newResponseDumper(resp http.ResponseWriter) *responseDumper {\n\tbuf := new(bytes.Buffer)\n\treturn &responseDumper{\n\t\tResponseWriter: resp,\n\n\t\tmw:  io.MultiWriter(resp, buf),\n\t\tbuf: buf,\n\t}\n}\n\nfunc (d *responseDumper) Write(b []byte) (int, error) {\n\treturn d.mw.Write(b)\n}\n\nfunc (d *responseDumper) GetResponse() string {\n\treturn d.buf.String()\n}\n\nfunc (d *responseDumper) Unwrap() http.ResponseWriter {\n\treturn d.ResponseWriter\n}\n"
  },
  {
    "path": "pprof/README.md",
    "content": "Usage\n\n```go\npackage main\n\nimport (\n\t\"log/slog\"\n\n\t\"github.com/labstack/echo-contrib/v5/pprof\"\n\t\"github.com/labstack/echo/v5\"\n)\n\nfunc main() {\n\te := echo.New()\n\tpprof.Register(e)\n\t//......\n\tif err := e.Start(\":1323\"); err != nil {\n\t\tslog.Error(\"failed to start server\", \"error\", err)\n\t}\n}\n```\n\n- Then use the pprof tool to look at the heap profile:\n\n    `go tool pprof http://localhost:1323/debug/pprof/heap`\n\n-  Or to look at a 30-second CPU profile:\n    \n    `go tool pprof http://localhost:1323/debug/pprof/profile?seconds=30`\n\n- Or to look at the goroutine blocking profile, after calling runtime.SetBlockProfileRate in your program:\n    \n    `go tool pprof http://localhost:1323/debug/pprof/block`\n\n- Or to look at the holders of contended mutexes, after calling runtime.SetMutexProfileFraction in your program:\n    \n    `go tool pprof http://localhost:1323/debug/pprof/mutex`\n\n\n"
  },
  {
    "path": "pprof/pprof.go",
    "content": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage pprof\n\nimport (\n\t\"net/http\"\n\t\"net/http/pprof\"\n\n\t\"github.com/labstack/echo/v5\"\n)\n\nconst (\n\t// DefaultPrefix url prefix of pprof\n\tDefaultPrefix = \"/debug/pprof\"\n)\n\nfunc getPrefix(prefixOptions ...string) string {\n\tif len(prefixOptions) > 0 {\n\t\treturn prefixOptions[0]\n\t}\n\treturn DefaultPrefix\n}\n\n// Register middleware for net/http/pprof\nfunc Register(e *echo.Echo, prefixOptions ...string) {\n\tprefix := getPrefix(prefixOptions...)\n\n\tprefixRouter := e.Group(prefix)\n\t{\n\t\tprefixRouter.GET(\"/\", handler(pprof.Index))\n\t\tprefixRouter.GET(\"/allocs\", handler(pprof.Handler(\"allocs\").ServeHTTP))\n\t\tprefixRouter.GET(\"/block\", handler(pprof.Handler(\"block\").ServeHTTP))\n\t\tprefixRouter.GET(\"/cmdline\", handler(pprof.Cmdline))\n\t\tprefixRouter.GET(\"/goroutine\", handler(pprof.Handler(\"goroutine\").ServeHTTP))\n\t\tprefixRouter.GET(\"/heap\", handler(pprof.Handler(\"heap\").ServeHTTP))\n\t\tprefixRouter.GET(\"/mutex\", handler(pprof.Handler(\"mutex\").ServeHTTP))\n\t\tprefixRouter.GET(\"/profile\", handler(pprof.Profile))\n\t\tprefixRouter.POST(\"/symbol\", handler(pprof.Symbol))\n\t\tprefixRouter.GET(\"/symbol\", handler(pprof.Symbol))\n\t\tprefixRouter.GET(\"/threadcreate\", handler(pprof.Handler(\"threadcreate\").ServeHTTP))\n\t\tprefixRouter.GET(\"/trace\", handler(pprof.Trace))\n\t}\n}\n\nfunc handler(h http.HandlerFunc) echo.HandlerFunc {\n\treturn func(c *echo.Context) error {\n\t\th.ServeHTTP(c.Response(), c.Request())\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "pprof/pprof_test.go",
    "content": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage pprof\n\nimport (\n\t\"github.com/stretchr/testify/assert\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/labstack/echo/v5\"\n)\n\nfunc TestPProfRegisterDefaualtPrefix(t *testing.T) {\n\tvar pprofPaths = []struct {\n\t\tpath string\n\t}{\n\t\t{\"/\"},\n\t\t{\"/allocs\"},\n\t\t{\"/block\"},\n\t\t{\"/cmdline\"},\n\t\t{\"/goroutine\"},\n\t\t{\"/heap\"},\n\t\t{\"/mutex\"},\n\t\t{\"/profile?seconds=1\"},\n\t\t{\"/symbol\"},\n\t\t{\"/symbol\"},\n\t\t{\"/threadcreate\"},\n\t\t{\"/trace\"},\n\t}\n\tfor _, tt := range pprofPaths {\n\t\tt.Run(tt.path, func(t *testing.T) {\n\t\t\te := echo.New()\n\t\t\tRegister(e)\n\t\t\treq, _ := http.NewRequest(http.MethodGet, DefaultPrefix+tt.path, nil)\n\t\t\trec := httptest.NewRecorder()\n\t\t\te.ServeHTTP(rec, req)\n\t\t\tassert.Equal(t, rec.Code, http.StatusOK)\n\t\t})\n\t}\n}\n\nfunc TestPProfRegisterCustomPrefix(t *testing.T) {\n\tvar pprofPaths = []struct {\n\t\tpath string\n\t}{\n\t\t{\"/\"},\n\t\t{\"/allocs\"},\n\t\t{\"/block\"},\n\t\t{\"/cmdline\"},\n\t\t{\"/goroutine\"},\n\t\t{\"/heap\"},\n\t\t{\"/mutex\"},\n\t\t{\"/profile?seconds=1\"},\n\t\t{\"/symbol\"},\n\t\t{\"/symbol\"},\n\t\t{\"/threadcreate\"},\n\t\t{\"/trace\"},\n\t}\n\tfor _, tt := range pprofPaths {\n\t\tt.Run(tt.path, func(t *testing.T) {\n\t\t\te := echo.New()\n\t\t\tpprofPrefix := \"/myapp/pprof\"\n\t\t\tRegister(e, pprofPrefix)\n\t\t\treq, _ := http.NewRequest(http.MethodGet, pprofPrefix+tt.path, nil)\n\t\t\trec := httptest.NewRecorder()\n\t\t\te.ServeHTTP(rec, req)\n\t\t\tassert.Equal(t, rec.Code, http.StatusOK)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "session/session.go",
    "content": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage session\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gorilla/sessions\"\n\t\"github.com/labstack/echo/v5\"\n\t\"github.com/labstack/echo/v5/middleware\"\n)\n\ntype (\n\t// Config defines the config for Session middleware.\n\tConfig struct {\n\t\t// Skipper defines a function to skip middleware.\n\t\tSkipper middleware.Skipper\n\n\t\t// Session store.\n\t\t// Required.\n\t\tStore sessions.Store\n\t}\n)\n\nconst (\n\tkey = \"_session_store\"\n)\n\nvar (\n\t// DefaultConfig is the default Session middleware config.\n\tDefaultConfig = Config{\n\t\tSkipper: middleware.DefaultSkipper,\n\t}\n)\n\n// Get returns a named session.\nfunc Get(name string, c *echo.Context) (*sessions.Session, error) {\n\ts := c.Get(key)\n\tif s == nil {\n\t\treturn nil, fmt.Errorf(\"%q session store not found\", key)\n\t}\n\tstore := s.(sessions.Store)\n\treturn store.Get(c.Request(), name)\n}\n\n// Middleware returns a Session middleware.\nfunc Middleware(store sessions.Store) echo.MiddlewareFunc {\n\tc := DefaultConfig\n\tc.Store = store\n\treturn MiddlewareWithConfig(c)\n}\n\n// MiddlewareWithConfig returns a Sessions middleware with config.\n// See `Middleware()`.\nfunc MiddlewareWithConfig(config Config) echo.MiddlewareFunc {\n\t// Defaults\n\tif config.Skipper == nil {\n\t\tconfig.Skipper = DefaultConfig.Skipper\n\t}\n\tif config.Store == nil {\n\t\tpanic(\"echo: session middleware requires store\")\n\t}\n\n\treturn func(next echo.HandlerFunc) echo.HandlerFunc {\n\t\treturn func(c *echo.Context) error {\n\t\t\tif config.Skipper(c) {\n\t\t\t\treturn next(c)\n\t\t\t}\n\t\t\tc.Set(key, config.Store)\n\t\t\treturn next(c)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "session/session_test.go",
    "content": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage session\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/gorilla/sessions\"\n\t\"github.com/labstack/echo/v5\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestMiddleware(t *testing.T) {\n\te := echo.New()\n\treq := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\trec := httptest.NewRecorder()\n\tc := e.NewContext(req, rec)\n\thandler := func(c *echo.Context) error {\n\t\tsess, _ := Get(\"test\", c)\n\t\tsess.Options.Domain = \"labstack.com\"\n\t\tsess.Values[\"foo\"] = \"bar\"\n\t\tif err := sess.Save(c.Request(), c.Response()); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn c.String(http.StatusOK, \"test\")\n\t}\n\tstore := sessions.NewCookieStore([]byte(\"secret\"))\n\tconfig := Config{\n\t\tSkipper: func(c *echo.Context) bool {\n\t\t\treturn true\n\t\t},\n\t\tStore: store,\n\t}\n\n\t// Skipper\n\tmw := MiddlewareWithConfig(config)\n\th := mw(func(c *echo.Context) error {\n\t\treturn echo.ErrNotFound\n\t})\n\tassert.Error(t, h(c)) // 404\n\tassert.Nil(t, c.Get(key))\n\n\t// Panic\n\tconfig.Skipper = nil\n\tconfig.Store = nil\n\tassert.Panics(t, func() {\n\t\tMiddlewareWithConfig(config)\n\t})\n\n\t// Core\n\tmw = Middleware(store)\n\th = mw(handler)\n\tassert.NoError(t, h(c))\n\tassert.Contains(t, rec.Header().Get(echo.HeaderSetCookie), \"labstack.com\")\n\n}\n\nfunc TestGetSessionMissingStore(t *testing.T) {\n\te := echo.New()\n\treq := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\trec := httptest.NewRecorder()\n\tc := e.NewContext(req, rec)\n\t_, err := Get(\"test\", c)\n\n\tassert.EqualError(t, err, fmt.Sprintf(\"%q session store not found\", key))\n}\n"
  },
  {
    "path": "zipkintracing/README.md",
    "content": "# Tracing Library for Go\n\n> Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/openzipkin-contrib/zipkin-otel and https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/zipkinexporter\n> `Go app -> OTLP -> OpenTelemetry Collector -> Zipkin`\n\nThis library provides tracing for go using [Zipkin](https://zipkin.io/)\n \n## Usage\n\n### Server Tracing Middleware & http client tracing\n\n```go\npackage main\n\nimport (\n\t\"io\"\n\t\"log/slog\"\n\t\"net/http\"\n\n\t\"github.com/labstack/echo-contrib/v5/zipkintracing\"\n\t\"github.com/labstack/echo/v5\"\n\t\"github.com/openzipkin/zipkin-go\"\n\tzipkinhttp \"github.com/openzipkin/zipkin-go/middleware/http\"\n\tzipkinHttpReporter \"github.com/openzipkin/zipkin-go/reporter/http\"\n)\n\nfunc main() {\n\te := echo.New()\n\tendpoint, err := zipkin.NewEndpoint(\"echo-service\", \"\")\n\tif err != nil {\n\t\tslog.Error(\"failed to create zipkin endpoint\", \"error\", err)\n\t}\n\treporter := zipkinHttpReporter.NewReporter(\"http://localhost:9411/api/v2/spans\")\n\ttraceTags := make(map[string]string)\n\ttraceTags[\"availability_zone\"] = \"us-east-1\"\n\ttracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(endpoint), zipkin.WithTags(traceTags))\n\tclient, _ := zipkinhttp.NewClient(tracer, zipkinhttp.ClientTrace(true))\n\tif err != nil {\n\t\tslog.Error(\"failed to create tracer\", \"error\", err)\n\t}\n\t//Wrap & Use trace server middleware, this traces all server calls\n\te.Use(zipkintracing.TraceServer(tracer))\n\t//....\n\te.GET(\"/echo\", func(c *echo.Context) error {\n\t\t//trace http request calls.\n\t\treq, _ := http.NewRequest(\"GET\", \"https://echo.labstack.com/\", nil)\n\t\tresp, _ := zipkintracing.DoHTTP(c, req, client)\n\t\tbody, _ := io.ReadAll(resp.Body)\n\t\treturn c.String(http.StatusOK, string(body))\n\t})\n\n\tdefer reporter.Close() //defer close reporter\n\tif err := e.Start(\":8080\"); err != nil {\n\t\tslog.Error(\"failed to start server\", \"error\", err)\n\t}\n}\n\n```\n### Reverse Proxy Tracing\n\n```go\npackage main\n\nimport (\n\t\"log/slog\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n\n\t\"github.com/labstack/echo-contrib/v5/zipkintracing\"\n\t\"github.com/labstack/echo/v5\"\n\t\"github.com/openzipkin/zipkin-go\"\n\tzipkinHttpReporter \"github.com/openzipkin/zipkin-go/reporter/http\"\n)\n\nfunc main() {\n\te := echo.New()\n\t//new tracing instance\n\tendpoint, err := zipkin.NewEndpoint(\"echo-service\", \"\")\n\tif err != nil {\n\t\tslog.Error(\"failed to create endpoint\", \"error\", err)\n\t}\n\treporter := zipkinHttpReporter.NewReporter(\"http://localhost:9411/api/v2/spans\")\n\ttraceTags := make(map[string]string)\n\ttraceTags[\"availability_zone\"] = \"us-east-1\"\n\ttracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(endpoint), zipkin.WithTags(traceTags))\n\tif err != nil {\n\t\tslog.Error(\"failed to create tracer\", \"error\", err)\n\t}\n\t//....\n\te.GET(\"/echo\", func(c *echo.Context) error {\n\t\tproxyURL, _ := url.Parse(\"https://echo.labstack.com/\")\n\t\thttputil.NewSingleHostReverseProxy(proxyURL)\n\t\treturn nil\n\t}, zipkintracing.TraceProxy(tracer))\n\n\tdefer reporter.Close() //close reporter\n\tif err := e.Start(\":8080\"); err != nil {\n\t\tslog.Error(\"failed to start server\", \"error\", err)\n\t}\n}\n```\n\n### Trace function calls\n\nTo trace function calls e.g. to trace `s3Func`\n\n```go\npackage main\n\nimport (\n\t\"github.com/labstack/echo-contrib/v5/zipkintracing\"\n\t\"github.com/labstack/echo/v5\"\n\t\"github.com/openzipkin/zipkin-go\"\n)\n\nfunc s3Func(c *echo.Context, tracer *zipkin.Tracer) {\n\tdefer zipkintracing.TraceFunc(c, \"s3_read\", zipkintracing.DefaultSpanTags, tracer)()\n\t//s3Func logic here...\n}\n```\n\n### Create Child Span\n\n```go\npackage main\n\nimport (\n\t\"github.com/labstack/echo-contrib/v5/zipkintracing\"\n\t\"github.com/labstack/echo/v5\"\n\t\"github.com/openzipkin/zipkin-go\"\n)\n\nfunc traceWithChildSpan(c *echo.Context, tracer *zipkin.Tracer) {\n\tspan := zipkintracing.StartChildSpan(c, \"someMethod\", tracer)\n\t//func logic.....\n\tspan.Finish()\n}\n```"
  },
  {
    "path": "zipkintracing/response_writer.go",
    "content": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage zipkintracing\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"net\"\n\t\"net/http\"\n)\n\n// ResponseWriter is a wrapper around http.ResponseWriter that provides extra information about\n// the response. It is recommended that middleware handlers use this construct to wrap a response writer\n// if the functionality calls for it.\ntype ResponseWriter interface {\n\thttp.ResponseWriter\n\thttp.Flusher\n\t// Status returns the status code of the response or 0 if the response has\n\t// not been written\n\tStatus() int\n\t// Written returns whether or not the ResponseWriter has been written.\n\tWritten() bool\n\t// Size returns the size of the response body.\n\tSize() int\n\t// Before allows for a function to be called before the ResponseWriter has been written to. This is\n\t// useful for setting headers or any other operations that must happen before a response has been written.\n\tBefore(func(ResponseWriter))\n}\n\ntype beforeFunc func(ResponseWriter)\n\n// NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter\nfunc NewResponseWriter(rw http.ResponseWriter) ResponseWriter {\n\tnrw := &responseWriter{\n\t\tResponseWriter: rw,\n\t}\n\n\treturn nrw\n}\n\ntype responseWriter struct {\n\thttp.ResponseWriter\n\tstatus      int\n\tsize        int\n\tbeforeFuncs []beforeFunc\n}\n\nfunc (rw *responseWriter) WriteHeader(s int) {\n\trw.status = s\n\trw.callBefore()\n\trw.ResponseWriter.WriteHeader(s)\n}\n\nfunc (rw *responseWriter) Write(b []byte) (int, error) {\n\tif !rw.Written() {\n\t\t// The status will be StatusOK if WriteHeader has not been called yet\n\t\trw.WriteHeader(http.StatusOK)\n\t}\n\tsize, err := rw.ResponseWriter.Write(b)\n\trw.size += size\n\treturn size, err\n}\n\nfunc (rw *responseWriter) Status() int {\n\treturn rw.status\n}\n\nfunc (rw *responseWriter) Size() int {\n\treturn rw.size\n}\n\nfunc (rw *responseWriter) Written() bool {\n\treturn rw.status != 0\n}\n\nfunc (rw *responseWriter) Before(before func(ResponseWriter)) {\n\trw.beforeFuncs = append(rw.beforeFuncs, before)\n}\n\nfunc (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {\n\thijacker, ok := rw.ResponseWriter.(http.Hijacker)\n\tif !ok {\n\t\treturn nil, nil, errors.New(\"the ResponseWriter doesn't support the Hijacker interface\")\n\t}\n\treturn hijacker.Hijack()\n}\n\nfunc (rw *responseWriter) callBefore() {\n\tfor i := len(rw.beforeFuncs) - 1; i >= 0; i-- {\n\t\trw.beforeFuncs[i](rw)\n\t}\n}\n\nfunc (rw *responseWriter) Flush() {\n\tflusher, ok := rw.ResponseWriter.(http.Flusher)\n\tif ok {\n\t\tif !rw.Written() {\n\t\t\t// The status will be StatusOK if WriteHeader has not been called yet\n\t\t\trw.WriteHeader(http.StatusOK)\n\t\t}\n\t\tflusher.Flush()\n\t}\n}\n\nfunc (rw *responseWriter) CloseNotify() <-chan bool {\n\t//lint:ignore SA1019 we support it for backwards compatibility reasons\n\treturn rw.ResponseWriter.(http.CloseNotifier).CloseNotify()\n}\n\nfunc (rw *responseWriter) Unwrap() http.ResponseWriter {\n\treturn rw.ResponseWriter\n}\n"
  },
  {
    "path": "zipkintracing/tracing.go",
    "content": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage zipkintracing\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/labstack/echo/v5/middleware\"\n\n\t\"github.com/labstack/echo/v5\"\n\t\"github.com/openzipkin/zipkin-go\"\n\tzipkinhttp \"github.com/openzipkin/zipkin-go/middleware/http\"\n\t\"github.com/openzipkin/zipkin-go/model\"\n\t\"github.com/openzipkin/zipkin-go/propagation/b3\"\n)\n\ntype (\n\n\t//Tags func to adds span tags\n\tTags func(c *echo.Context) map[string]string\n\n\t//TraceProxyConfig config for TraceProxyWithConfig\n\tTraceProxyConfig struct {\n\t\tSkipper  middleware.Skipper\n\t\tTracer   *zipkin.Tracer\n\t\tSpanTags Tags\n\t}\n\n\t//TraceServerConfig config for TraceServerWithConfig\n\tTraceServerConfig struct {\n\t\tSkipper  middleware.Skipper\n\t\tTracer   *zipkin.Tracer\n\t\tSpanTags Tags\n\t}\n)\n\nvar (\n\t//DefaultSpanTags default span tags\n\tDefaultSpanTags = func(c *echo.Context) map[string]string {\n\t\treturn make(map[string]string)\n\t}\n\n\t//DefaultTraceProxyConfig default config for Trace Proxy\n\tDefaultTraceProxyConfig = TraceProxyConfig{Skipper: middleware.DefaultSkipper, SpanTags: DefaultSpanTags}\n\n\t//DefaultTraceServerConfig default config for Trace Server\n\tDefaultTraceServerConfig = TraceServerConfig{Skipper: middleware.DefaultSkipper, SpanTags: DefaultSpanTags}\n)\n\n// DoHTTP is a http zipkin tracer implementation of HTTPDoer\n//\n// Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/openzipkin-contrib/zipkin-otel and https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/zipkinexporter\nfunc DoHTTP(c *echo.Context, r *http.Request, client *zipkinhttp.Client) (*http.Response, error) {\n\treq := r.WithContext(c.Request().Context())\n\treturn client.DoWithAppSpan(req, req.Method)\n}\n\n// TraceFunc wraps function call with span so that we can trace time taken by func, eventContext only provided if we want to store trace headers\n//\n// Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/openzipkin-contrib/zipkin-otel and https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/zipkinexporter\nfunc TraceFunc(c *echo.Context, spanName string, spanTags Tags, tracer *zipkin.Tracer) func() {\n\tspan, _ := tracer.StartSpanFromContext(c.Request().Context(), spanName)\n\tfor key, value := range spanTags(c) {\n\t\tspan.Tag(key, value)\n\t}\n\n\tfinishSpan := func() {\n\t\tspan.Finish()\n\t}\n\n\treturn finishSpan\n}\n\n// TraceProxy middleware that traces reverse proxy\n//\n// Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/openzipkin-contrib/zipkin-otel and https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/zipkinexporter\nfunc TraceProxy(tracer *zipkin.Tracer) echo.MiddlewareFunc {\n\tconfig := DefaultTraceProxyConfig\n\tconfig.Tracer = tracer\n\treturn TraceProxyWithConfig(config)\n}\n\n// TraceProxyWithConfig middleware that traces reverse proxy\n//\n// Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/openzipkin-contrib/zipkin-otel and https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/zipkinexporter\nfunc TraceProxyWithConfig(config TraceProxyConfig) echo.MiddlewareFunc {\n\treturn func(next echo.HandlerFunc) echo.HandlerFunc {\n\t\treturn func(c *echo.Context) error {\n\t\t\tif config.Skipper(c) {\n\t\t\t\treturn next(c)\n\t\t\t}\n\t\t\tvar parentContext model.SpanContext\n\t\t\tif span := zipkin.SpanFromContext(c.Request().Context()); span != nil {\n\t\t\t\tparentContext = span.Context()\n\t\t\t}\n\t\t\tspan := config.Tracer.StartSpan(fmt.Sprintf(\"C %s %s\", c.Request().Method, \"reverse proxy\"), zipkin.Parent(parentContext))\n\t\t\tfor key, value := range config.SpanTags(c) {\n\t\t\t\tspan.Tag(key, value)\n\t\t\t}\n\t\t\tdefer span.Finish()\n\t\t\tctx := zipkin.NewContext(c.Request().Context(), span)\n\t\t\tc.SetRequest(c.Request().WithContext(ctx))\n\t\t\tb3.InjectHTTP(c.Request())(span.Context())\n\t\t\tnrw := NewResponseWriter(c.Response())\n\t\t\tif err := next(c); err != nil {\n\t\t\t\tc.Echo().HTTPErrorHandler(c, err)\n\t\t\t}\n\t\t\tif nrw.Size() > 0 {\n\t\t\t\tzipkin.TagHTTPResponseSize.Set(span, strconv.FormatInt(int64(nrw.Size()), 10))\n\t\t\t}\n\t\t\tif nrw.Status() < 200 || nrw.Status() > 299 {\n\t\t\t\tstatusCode := strconv.FormatInt(int64(nrw.Status()), 10)\n\t\t\t\tzipkin.TagHTTPStatusCode.Set(span, statusCode)\n\t\t\t\tif nrw.Status() > 399 {\n\t\t\t\t\tzipkin.TagError.Set(span, statusCode)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\n// TraceServer middleware that traces server calls\n//\n// Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/openzipkin-contrib/zipkin-otel and https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/zipkinexporter\nfunc TraceServer(tracer *zipkin.Tracer) echo.MiddlewareFunc {\n\tconfig := DefaultTraceServerConfig\n\tconfig.Tracer = tracer\n\treturn TraceServerWithConfig(config)\n}\n\n// TraceServerWithConfig middleware that traces server calls\n//\n// Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/openzipkin-contrib/zipkin-otel and https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/zipkinexporter\nfunc TraceServerWithConfig(config TraceServerConfig) echo.MiddlewareFunc {\n\treturn func(next echo.HandlerFunc) echo.HandlerFunc {\n\t\treturn func(c *echo.Context) error {\n\t\t\tif config.Skipper(c) {\n\t\t\t\treturn next(c)\n\t\t\t}\n\t\t\tsc := config.Tracer.Extract(b3.ExtractHTTP(c.Request()))\n\t\t\tspan := config.Tracer.StartSpan(fmt.Sprintf(\"S %s %s\", c.Request().Method, c.Request().URL.Path), zipkin.Parent(sc))\n\t\t\tfor key, value := range config.SpanTags(c) {\n\t\t\t\tspan.Tag(key, value)\n\t\t\t}\n\t\t\tdefer span.Finish()\n\t\t\tctx := zipkin.NewContext(c.Request().Context(), span)\n\t\t\tc.SetRequest(c.Request().WithContext(ctx))\n\t\t\tnrw := NewResponseWriter(c.Response())\n\t\t\tif err := next(c); err != nil {\n\t\t\t\tc.Echo().HTTPErrorHandler(c, err)\n\t\t\t}\n\n\t\t\tif nrw.Size() > 0 {\n\t\t\t\tzipkin.TagHTTPResponseSize.Set(span, strconv.FormatInt(int64(nrw.Size()), 10))\n\t\t\t}\n\t\t\tif nrw.Status() < 200 || nrw.Status() > 299 {\n\t\t\t\tstatusCode := strconv.FormatInt(int64(nrw.Status()), 10)\n\t\t\t\tzipkin.TagHTTPStatusCode.Set(span, statusCode)\n\t\t\t\tif nrw.Status() > 399 {\n\t\t\t\t\tzipkin.TagError.Set(span, statusCode)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\n// StartChildSpan starts a new child span as child of parent span from context\n// user must call defer childSpan.Finish()\nfunc StartChildSpan(c *echo.Context, spanName string, tracer *zipkin.Tracer) (childSpan zipkin.Span) {\n\tvar parentContext model.SpanContext\n\n\tif span := zipkin.SpanFromContext(c.Request().Context()); span != nil {\n\t\tparentContext = span.Context()\n\t}\n\tchildSpan = tracer.StartSpan(spanName, zipkin.Parent(parentContext))\n\treturn childSpan\n}\n"
  },
  {
    "path": "zipkintracing/tracing_test.go",
    "content": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage zipkintracing\n\nimport (\n\t\"encoding/json\"\n\t\"github.com/labstack/echo/v5\"\n\t\"github.com/labstack/echo/v5/middleware\"\n\t\"github.com/openzipkin/zipkin-go\"\n\tzipkinhttp \"github.com/openzipkin/zipkin-go/middleware/http\"\n\t\"github.com/openzipkin/zipkin-go/propagation/b3\"\n\t\"github.com/openzipkin/zipkin-go/reporter\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n\n\tzipkinHttpReporter \"github.com/openzipkin/zipkin-go/reporter/http\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype zipkinSpanRequest struct {\n\tID            string\n\tTraceID       string\n\tTimestamp     uint64\n\tName          string\n\tLocalEndpoint struct {\n\t\tServiceName string\n\t}\n\tTags map[string]string\n}\n\n// DefaultTracer returns zipkin tracer with defaults for testing\nfunc DefaultTracer(reportingURL, serviceName string, tags map[string]string) (*zipkin.Tracer, reporter.Reporter, error) {\n\tendpoint, err := zipkin.NewEndpoint(serviceName, \"\")\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treporter := zipkinHttpReporter.NewReporter(reportingURL)\n\ttracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(endpoint), zipkin.WithTags(tags))\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treturn tracer, reporter, nil\n}\n\nfunc TestDoHTTTP(t *testing.T) {\n\tdone := make(chan struct{})\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tdefer close(done)\n\n\t\tbody, err := ioutil.ReadAll(r.Body)\n\t\tassert.NoError(t, err)\n\n\t\tvar spans []zipkinSpanRequest\n\t\terr = json.Unmarshal(body, &spans)\n\t\tassert.NoError(t, err)\n\n\t\tassert.NotEmpty(t, spans[0].ID)\n\t\tassert.NotEmpty(t, spans[0].TraceID)\n\t\tassert.Equal(t, \"http/get\", spans[0].Name)\n\t\tassert.Equal(t, \"echo-service\", spans[0].LocalEndpoint.ServiceName)\n\t}))\n\tdefer ts.Close()\n\n\techoServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tassert.Equal(t, r.Method, http.MethodGet)\n\t\tassert.NotEmpty(t, r.Header.Get(b3.TraceID))\n\t\tassert.NotEmpty(t, r.Header.Get(b3.SpanID))\n\t}))\n\tdefer echoServer.Close()\n\ttracer, reporter, err := DefaultTracer(ts.URL, \"echo-service\", nil)\n\treq := httptest.NewRequest(http.MethodGet, echoServer.URL, nil)\n\treq.RequestURI = \"\"\n\trec := httptest.NewRecorder()\n\tassert.NoError(t, err)\n\te := echo.New()\n\tc := e.NewContext(req, rec)\n\tclient, err := zipkinhttp.NewClient(tracer)\n\tassert.NoError(t, err)\n\t_, err = DoHTTP(c, req, client)\n\tassert.NoError(t, err)\n\terr = reporter.Close()\n\tassert.NoError(t, err)\n\n\tselect {\n\tcase <-done:\n\tcase <-time.After(time.Millisecond * 1500):\n\t\tt.Fatalf(\"Test server did not receive spans\")\n\t}\n}\n\nfunc TestTraceFunc(t *testing.T) {\n\tdone := make(chan struct{})\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tdefer close(done)\n\n\t\tbody, err := ioutil.ReadAll(r.Body)\n\t\tassert.NoError(t, err)\n\n\t\tvar spans []zipkinSpanRequest\n\t\terr = json.Unmarshal(body, &spans)\n\t\tassert.NoError(t, err)\n\t\tassert.NotEmpty(t, spans[0].ID)\n\t\tassert.NotEmpty(t, spans[0].TraceID)\n\t\tassert.Equal(t, \"s3_read\", spans[0].Name)\n\t\tassert.Equal(t, \"echo-service\", spans[0].LocalEndpoint.ServiceName)\n\t\tassert.NotNil(t, spans[0].Tags[\"availability_zone\"])\n\t\tassert.Equal(t, \"us-east-1\", spans[0].Tags[\"availability_zone\"])\n\t}))\n\tdefer ts.Close()\n\te := echo.New()\n\treq := httptest.NewRequest(\"GET\", \"http://localhost:8080/echo\", nil)\n\trec := httptest.NewRecorder()\n\tc := e.NewContext(req, rec)\n\ttraceTags := make(map[string]string)\n\ttraceTags[\"availability_zone\"] = \"us-east-1\"\n\ttracer, reporter, err := DefaultTracer(ts.URL, \"echo-service\", traceTags)\n\tassert.NoError(t, err)\n\ts3func := func(name string) {\n\t\tTraceFunc(c, \"s3_read\", DefaultSpanTags, tracer)()\n\t\tassert.Equal(t, \"s3Test\", name)\n\t}\n\ts3func(\"s3Test\")\n\terr = reporter.Close()\n\tassert.NoError(t, err)\n\tselect {\n\tcase <-done:\n\tcase <-time.After(time.Millisecond * 15500):\n\t\tt.Fatalf(\"Test server did not receive spans\")\n\t}\n}\n\nfunc TestTraceProxy(t *testing.T) {\n\tdone := make(chan struct{})\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tdefer close(done)\n\n\t\tbody, err := ioutil.ReadAll(r.Body)\n\t\tassert.NoError(t, err)\n\n\t\tvar spans []zipkinSpanRequest\n\t\terr = json.Unmarshal(body, &spans)\n\t\tassert.NoError(t, err)\n\n\t\tassert.NotEmpty(t, spans[0].ID)\n\t\tassert.NotEmpty(t, spans[0].TraceID)\n\t\tassert.Equal(t, \"c get reverse proxy\", spans[0].Name)\n\t\tassert.Equal(t, \"echo-service\", spans[0].LocalEndpoint.ServiceName)\n\t\tassert.NotNil(t, spans[0].Tags[\"availability_zone\"])\n\t\tassert.Equal(t, \"us-east-1\", spans[0].Tags[\"availability_zone\"])\n\t}))\n\tdefer ts.Close()\n\ttraceTags := make(map[string]string)\n\ttraceTags[\"availability_zone\"] = \"us-east-1\"\n\ttracer, reporter, err := DefaultTracer(ts.URL, \"echo-service\", traceTags)\n\treq := httptest.NewRequest(\"GET\", \"http://localhost:8080/accounts/acctrefid/transactions\", nil)\n\trec := httptest.NewRecorder()\n\te := echo.New()\n\tc := e.NewContext(req, rec)\n\tmw := TraceProxy(tracer)\n\th := mw(func(c *echo.Context) error {\n\t\treturn nil\n\t})\n\terr = h(c)\n\tassert.NoError(t, err)\n\tassert.NotEmpty(t, req.Header.Get(b3.TraceID))\n\tassert.NotEmpty(t, req.Header.Get(b3.SpanID))\n\n\terr = reporter.Close()\n\tassert.NoError(t, err)\n\n\tselect {\n\tcase <-done:\n\tcase <-time.After(time.Millisecond * 1500):\n\t\tt.Fatalf(\"Test server did not receive spans\")\n\t}\n}\n\nfunc TestTraceServer(t *testing.T) {\n\tdone := make(chan struct{})\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tdefer close(done)\n\n\t\tbody, err := ioutil.ReadAll(r.Body)\n\t\tassert.NoError(t, err)\n\n\t\tvar spans []zipkinSpanRequest\n\t\terr = json.Unmarshal(body, &spans)\n\t\tassert.NoError(t, err)\n\n\t\tassert.NotEmpty(t, spans[0].ID)\n\t\tassert.NotEmpty(t, spans[0].TraceID)\n\t\tassert.Equal(t, \"s get /accounts/acctrefid/transactions\", spans[0].Name)\n\t\tassert.Equal(t, \"echo-service\", spans[0].LocalEndpoint.ServiceName)\n\t\tassert.NotNil(t, spans[0].Tags[\"availability_zone\"])\n\t\tassert.Equal(t, \"us-east-1\", spans[0].Tags[\"availability_zone\"])\n\t}))\n\tdefer ts.Close()\n\ttraceTags := make(map[string]string)\n\ttraceTags[\"availability_zone\"] = \"us-east-1\"\n\ttracer, reporter, err := DefaultTracer(ts.URL, \"echo-service\", traceTags)\n\treq := httptest.NewRequest(\"GET\", \"http://localhost:8080/accounts/acctrefid/transactions\", nil)\n\trec := httptest.NewRecorder()\n\tmw := TraceServer(tracer)\n\th := mw(func(c *echo.Context) error {\n\t\treturn nil\n\t})\n\tassert.NoError(t, err)\n\te := echo.New()\n\tc := e.NewContext(req, rec)\n\terr = h(c)\n\terr = reporter.Close()\n\tassert.NoError(t, err)\n\n\tselect {\n\tcase <-done:\n\tcase <-time.After(time.Millisecond * 1500):\n\t\tt.Fatalf(\"Test server did not receive spans\")\n\t}\n}\n\nfunc TestTraceServerWithConfig(t *testing.T) {\n\tdone := make(chan struct{})\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tdefer close(done)\n\n\t\tbody, err := ioutil.ReadAll(r.Body)\n\t\tassert.NoError(t, err)\n\n\t\tvar spans []zipkinSpanRequest\n\t\terr = json.Unmarshal(body, &spans)\n\t\tassert.NoError(t, err)\n\n\t\tassert.NotEmpty(t, spans[0].ID)\n\t\tassert.NotEmpty(t, spans[0].TraceID)\n\t\tassert.Equal(t, \"s get /accounts/acctrefid/transactions\", spans[0].Name)\n\t\tassert.Equal(t, \"echo-service\", spans[0].LocalEndpoint.ServiceName)\n\t\tassert.NotNil(t, spans[0].Tags[\"availability_zone\"])\n\t\tassert.Equal(t, \"us-east-1\", spans[0].Tags[\"availability_zone\"])\n\t\tassert.NotNil(t, spans[0].Tags[\"Client-Correlation-Id\"])\n\t\tassert.Equal(t, \"c98404736319\", spans[0].Tags[\"Client-Correlation-Id\"])\n\n\t}))\n\tdefer ts.Close()\n\ttraceTags := make(map[string]string)\n\ttraceTags[\"availability_zone\"] = \"us-east-1\"\n\ttracer, reporter, err := DefaultTracer(ts.URL, \"echo-service\", traceTags)\n\treq := httptest.NewRequest(\"GET\", \"http://localhost:8080/accounts/acctrefid/transactions\", nil)\n\treq.Header.Add(\"Client-Correlation-Id\", \"c98404736319\")\n\trec := httptest.NewRecorder()\n\ttags := func(c *echo.Context) map[string]string {\n\t\ttags := make(map[string]string)\n\t\tcorrelationID := c.Request().Header.Get(\"Client-Correlation-Id\")\n\t\ttags[\"Client-Correlation-Id\"] = correlationID\n\t\treturn tags\n\t}\n\tconfig := TraceServerConfig{Skipper: middleware.DefaultSkipper, SpanTags: tags, Tracer: tracer}\n\tmw := TraceServerWithConfig(config)\n\th := mw(func(c *echo.Context) error {\n\t\treturn nil\n\t})\n\tassert.NoError(t, err)\n\te := echo.New()\n\tc := e.NewContext(req, rec)\n\terr = h(c)\n\terr = reporter.Close()\n\tassert.NoError(t, err)\n\tselect {\n\tcase <-done:\n\tcase <-time.After(time.Millisecond * 1500):\n\t\tt.Fatalf(\"Test server did not receive spans\")\n\t}\n}\n\nfunc TestTraceServerWithConfigSkipper(t *testing.T) {\n\tdone := make(chan struct{})\n\tneverCalled := false\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tdefer close(done)\n\t\tbody, err := ioutil.ReadAll(r.Body)\n\t\tassert.NoError(t, err)\n\t\tvar spans []zipkinSpanRequest\n\t\terr = json.Unmarshal(body, &spans)\n\t\tassert.NoError(t, err)\n\t}))\n\tdefer ts.Close()\n\ttraceTags := make(map[string]string)\n\ttracer, reporter, err := DefaultTracer(ts.URL, \"echo-service\", traceTags)\n\ttraceTags[\"availability_zone\"] = \"us-east-1\"\n\treq := httptest.NewRequest(\"GET\", \"http://localhost:8080/health\", nil)\n\trec := httptest.NewRecorder()\n\tconfig := TraceServerConfig{Skipper: func(c *echo.Context) bool {\n\t\treturn c.Request().URL.Path == \"/health\"\n\t}, Tracer: tracer}\n\tmw := TraceServerWithConfig(config)\n\th := mw(func(c *echo.Context) error {\n\t\treturn nil\n\t})\n\tassert.NoError(t, err)\n\te := echo.New()\n\tc := e.NewContext(req, rec)\n\terr = h(c)\n\terr = reporter.Close()\n\tassert.NoError(t, err)\n\tselect {\n\tcase <-done:\n\tcase <-time.After(time.Millisecond * 500):\n\t\tneverCalled = true\n\t}\n\tassert.True(t, neverCalled)\n}\n\nfunc TestStartChildSpan(t *testing.T) {\n\tdone := make(chan struct{})\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tdefer close(done)\n\n\t\tbody, err := ioutil.ReadAll(r.Body)\n\t\tassert.NoError(t, err)\n\n\t\tvar spans []zipkinSpanRequest\n\t\terr = json.Unmarshal(body, &spans)\n\t\tassert.NoError(t, err)\n\t\tassert.NotEmpty(t, spans[0].ID)\n\t\tassert.NotEmpty(t, spans[0].TraceID)\n\t\tassert.Equal(t, \"kinesis-test\", spans[0].Name)\n\t\tassert.Equal(t, \"echo-service\", spans[0].LocalEndpoint.ServiceName)\n\t\tassert.NotNil(t, spans[0].Tags[\"availability_zone\"])\n\t\tassert.Equal(t, \"us-east-1\", spans[0].Tags[\"availability_zone\"])\n\t}))\n\tdefer ts.Close()\n\ttraceTags := make(map[string]string)\n\ttraceTags[\"availability_zone\"] = \"us-east-1\"\n\ttracer, reporter, err := DefaultTracer(ts.URL, \"echo-service\", traceTags)\n\tassert.NoError(t, err)\n\n\treq := httptest.NewRequest(\"GET\", \"http://localhost:8080/health\", nil)\n\trec := httptest.NewRecorder()\n\te := echo.New()\n\tc := e.NewContext(req, rec)\n\n\tchildSpan := StartChildSpan(c, \"kinesis-test\", tracer)\n\ttime.Sleep(500)\n\tchildSpan.Finish()\n\tassert.NoError(t, err)\n\terr = reporter.Close()\n\tassert.NoError(t, err)\n\tselect {\n\tcase <-done:\n\tcase <-time.After(time.Millisecond * 15500):\n\t\tt.Fatalf(\"Test server did not receive spans\")\n\t}\n}\n"
  }
]