Full Code of labstack/echo-contrib for AI

master 0203010ac803 cached
33 files
116.9 KB
35.1k tokens
149 symbols
1 requests
Download .txt
Repository: labstack/echo-contrib
Branch: master
Commit: 0203010ac803
Files: 33
Total size: 116.9 KB

Directory structure:
gitextract_lu6se63q/

├── .github/
│   ├── ISSUE_TEMPLATE.md
│   ├── stale.yml
│   └── workflows/
│       └── echo-contrib.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── casbin/
│   ├── README.md
│   ├── auth_model.conf
│   ├── auth_policy.csv
│   ├── broken_auth_model.conf
│   ├── casbin.go
│   └── casbin_test.go
├── codecov.yml
├── echo.go
├── echoprometheus/
│   ├── README.md
│   ├── prometheus.go
│   └── prometheus_test.go
├── go.mod
├── go.sum
├── internal/
│   └── helpers/
│       └── statuscode.go
├── jaegertracing/
│   ├── jaegertracing.go
│   ├── jaegertracing_test.go
│   └── response_dumper.go
├── pprof/
│   ├── README.md
│   ├── pprof.go
│   └── pprof_test.go
├── session/
│   ├── session.go
│   └── session_test.go
└── zipkintracing/
    ├── README.md
    ├── response_writer.go
    ├── tracing.go
    └── tracing_test.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
### Issue Description

### Checklist

- [ ] Dependencies installed
- [ ] No typos
- [ ] Searched existing issues and docs

### Expected behaviour

### Actual behaviour

### Steps to reproduce

### Working code to debug

```go
package main

func main() {
}
```

### Version/commit


================================================
FILE: .github/stale.yml
================================================
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 30
# Issues with these labels will never be considered stale
exemptLabels:
  - pinned
  - security
  - bug
  - enhancement
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
  This issue has been automatically marked as stale because it has not had
  recent activity. It will be closed within a month if no further activity occurs.
  Thank you for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false


================================================
FILE: .github/workflows/echo-contrib.yml
================================================
name: Run Tests

on:
  push:
    branches:
      - master
  pull_request:
    branches:
      - master
  workflow_dispatch:

permissions:
  contents: read #  to fetch code (actions/checkout)

env:
  # run coverage and benchmarks only with the latest Go version
  LATEST_GO_VERSION: "1.26"

jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        # Each major Go release is supported until there are two newer major releases. https://golang.org/doc/devel/release.html#policy
        # Echo CORE tests with last four major releases (unless there are pressing vulnerabilities)
        # As we depend on MANY DIFFERENT libraries which of SOME support last 2 Go releases we could have situations when
        # we derive from last four major releases promise.
        go: ["1.25", "1.26"]
    name: ${{ matrix.os }} @ Go ${{ matrix.go }}
    runs-on: ${{ matrix.os }}
    steps:
      - name: Checkout Code
        uses: actions/checkout@v6

      - name: Set up Go ${{ matrix.go }}
        uses: actions/setup-go@v6
        with:
          go-version: ${{ matrix.go }}

      - name: Run Tests
        run: go test -race --coverprofile=coverage.coverprofile --covermode=atomic ./...

      - name: Upload coverage to Codecov
        if: success() && matrix.go == env.LATEST_GO_VERSION && matrix.os == 'ubuntu-latest'
        uses: codecov/codecov-action@v5
        with:
          token:
          fail_ci_if_error: false

  benchmark:
    needs: test
    name: Benchmark comparison
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code (Previous)
        uses: actions/checkout@v6
        with:
          ref: ${{ github.base_ref }}
          path: previous

      - name: Checkout Code (New)
        uses: actions/checkout@v6
        with:
          path: new

      - name: Set up Go ${{ matrix.go }}
        uses: actions/setup-go@v6
        with:
          go-version: ${{ env.LATEST_GO_VERSION }}

      - name: Install Dependencies
        run: go install golang.org/x/perf/cmd/benchstat@latest

      - name: Run Benchmark (Previous)
        run: |
          cd previous
          go test -run="-" -bench=".*" -count=8 ./... > benchmark.txt

      - name: Run Benchmark (New)
        run: |
          cd new
          go test -run="-" -bench=".*" -count=8 ./... > benchmark.txt

      - name: Run Benchstat
        run: |
          benchstat previous/benchmark.txt new/benchmark.txt

================================================
FILE: .gitignore
================================================
vendor
_test
coverage.txt
.idea

================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2017 LabStack

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
PKG := "github.com/labstack/echo-contrib"
PKG_LIST := $(shell go list ${PKG}/...)

.DEFAULT_GOAL := check
check: lint vet race ## Check project

init:
	@go install honnef.co/go/tools/cmd/staticcheck@latest

format: ## Format the source code
	@find ./ -type f -name "*.go" -exec gofmt -w {} \;

lint: ## Lint the files
	@staticcheck -tests=false ${PKG_LIST}

vet: ## Vet the files
	@go vet ${PKG_LIST}

test: ## Run tests
	@go test -short ${PKG_LIST}

race: ## Run tests with data race detector
	@go test -race ${PKG_LIST}

benchmark: ## Run benchmarks
	@go test -run="-" -bench=".*" ${PKG_LIST}

help: ## Display this help screen
	@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

goversion ?= "1.18"
test_version: ## Run tests inside Docker with given version (defaults to 1.18 oldest supported). Example: make test_version goversion=1.18
	@docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make race"


================================================
FILE: README.md
================================================
# Echo Community Contribution middlewares

 [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/labstack/echo-contrib)
 [![Codecov](https://img.shields.io/codecov/c/github/labstack/echo-contrib.svg?style=flat-square)](https://codecov.io/gh/labstack/echo-contrib)
 [![Twitter](https://img.shields.io/badge/twitter-@labstack-55acee.svg?style=flat-square)](https://twitter.com/labstack)

* [Official website](https://echo.labstack.com)
* [All middleware docs](https://echo.labstack.com/docs/category/middleware)

## Deprecations / alternatives

1. Prometheus middleware has a new separate repository - https://github.com/labstack/echo-prometheus
2. Jaeger middleware is deprecated, use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + [OTLP exporters](https://opentelemetry.io/docs/languages/go/exporters/).
3. Zipkin middleware is deprecated, use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + [OTLP exporters](https://opentelemetry.io/docs/languages/go/exporters/).

## Usage

For Echo `v5` support:
```bash
go get github.com/labstack/echo-contrib/v5
```

## Versioning

This repository does not use semantic versioning. MAJOR version tracks which Echo version should be used. MINOR version
tracks API changes (possibly backwards incompatible, which is a very rare occasion), and a PATCH version is incremented for fixes.

> **Always add at least one integration test in your project.**

Minimal needed Echo versions:

* `v5.x.y` needs Echo `v5.0.0+`, use `go get github.com/labstack/echo-contrib/v5@latest`
* `v0.18.0` needs Echo `v4.15.0+`, use `go get github.com/labstack/echo-contrib@v0`

For `v0.x.y` releases the code is located in `v4` branch.

# Supported Go version

Each major Go release is supported until there are two newer major releases. https://golang.org/doc/devel/release.html#policy


[Echo CORE](https://github.com/labstack/echo) tests with last FOUR major releases (unless there are pressing vulnerabilities)
As this library depends on MANY DIFFERENT libraries which of SOME support only last 2 Go releases we could have situations when
we derive from last four major releases promise.

p.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/


================================================
FILE: casbin/README.md
================================================
# Usage
Simple example:
```go
package main

import (
	"log/slog"

	"github.com/casbin/casbin/v2"
	casbin_mw "github.com/labstack/echo-contrib/v5/casbin"
	"github.com/labstack/echo/v5"
)

func main() {
	e := echo.New()

	// Mediate the access for every request
	enforcer, err := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
	if err != nil {
		slog.Error("failed to load casbin enforcer", "error", err)
	}
	e.Use(casbin_mw.Middleware(enforcer))

	if err := e.Start(":1323"); err != nil {
		slog.Error("failed to start server", "error", err)
	}
}

```

Advanced example:
```go
package main

import (
	"log/slog"

	"github.com/casbin/casbin/v2"
	casbin_mw "github.com/labstack/echo-contrib/v5/casbin"
	"github.com/labstack/echo/v5"
)

func main() {
	ce, _ := casbin.NewEnforcer("auth_model.conf", "")
	ce.AddRoleForUser("alice", "admin")
	ce.AddPolicy(...)

	e := echo.New()

	e.Use(casbin_mw.Middleware(ce))

	if err := e.Start(":1323"); err != nil {
		slog.Error("failed to start server", "error", err)
	}
}
```

# API Reference
See [API Overview](https://casbin.org/docs/api-overview).

================================================
FILE: casbin/auth_model.conf
================================================
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")


================================================
FILE: casbin/auth_policy.csv
================================================
p, alice, /dataset1/*, GET
p, alice, /dataset1/resource1, POST
p, bob, /dataset2/resource1, *
p, bob, /dataset2/resource2, GET
p, bob, /dataset2/folder1/*, POST
p, dataset1_admin, /dataset1/*, *
g, cathy, dataset1_admin


================================================
FILE: casbin/broken_auth_model.conf
================================================
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")


================================================
FILE: casbin/casbin.go
================================================
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors

/* Package casbin provides middleware to enable ACL, RBAC, ABAC authorization support.

Simple example:

	package main

	import (
		"github.com/casbin/casbin/v2"
		"github.com/labstack/echo/v5"
		casbin_mw "github.com/labstack/echo-contrib/v5/casbin"
	)

	func main() {
		e := echo.New()

		// Mediate the access for every request
		e.Use(casbin_mw.Middleware(casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")))

		e.Logger.Fatal(e.Start(":1323"))
	}

Advanced example:

	package main

	import (
		"github.com/casbin/casbin/v2"
		"github.com/labstack/echo/v5"
		casbin_mw "github.com/labstack/echo-contrib/v5/casbin"
	)

	func main() {
		ce, _ := casbin.NewEnforcer("auth_model.conf", "")
		ce.AddRoleForUser("alice", "admin")
		ce.AddPolicy(...)

		e := echo.New()

		e.Use(casbin_mw.Middleware(ce))

		e.Logger.Fatal(e.Start(":1323"))
	}
*/

package casbin

import (
	"errors"
	"net/http"

	"github.com/casbin/casbin/v2"
	"github.com/labstack/echo/v5"
	"github.com/labstack/echo/v5/middleware"
)

type (
	// Config defines the config for CasbinAuth middleware.
	Config struct {
		// Skipper defines a function to skip middleware.
		Skipper middleware.Skipper

		// Enforcer CasbinAuth main rule.
		// One of Enforcer or EnforceHandler fields is required.
		Enforcer *casbin.Enforcer

		// EnforceHandler is custom callback to handle enforcing.
		// One of Enforcer or EnforceHandler fields is required.
		EnforceHandler func(c *echo.Context, user string) (bool, error)

		// Method to get the username - defaults to using basic auth
		UserGetter func(c *echo.Context) (string, error)

		// Method to handle errors
		ErrorHandler func(c *echo.Context, internal error, proposedStatus int) error
	}
)

var (
	// DefaultConfig is the default CasbinAuth middleware config.
	DefaultConfig = Config{
		Skipper: middleware.DefaultSkipper,
		UserGetter: func(c *echo.Context) (string, error) {
			username, _, _ := c.Request().BasicAuth()
			return username, nil
		},
		ErrorHandler: func(c *echo.Context, internal error, proposedStatus int) error {
			return echo.NewHTTPError(proposedStatus, internal.Error()).Wrap(internal)
		},
	}
)

// Middleware returns a CasbinAuth middleware.
//
// For valid credentials it calls the next handler.
// For missing or invalid credentials, it sends "401 - Unauthorized" response.
func Middleware(ce *casbin.Enforcer) echo.MiddlewareFunc {
	c := DefaultConfig
	c.Enforcer = ce
	return MiddlewareWithConfig(c)
}

// MiddlewareWithConfig returns a CasbinAuth middleware with config.
// See `Middleware()`.
func MiddlewareWithConfig(config Config) echo.MiddlewareFunc {
	if config.Enforcer == nil && config.EnforceHandler == nil {
		panic("one of casbin middleware Enforcer or EnforceHandler fields must be set")
	}
	if config.Skipper == nil {
		config.Skipper = DefaultConfig.Skipper
	}
	if config.UserGetter == nil {
		config.UserGetter = DefaultConfig.UserGetter
	}
	if config.ErrorHandler == nil {
		config.ErrorHandler = DefaultConfig.ErrorHandler
	}
	if config.EnforceHandler == nil {
		config.EnforceHandler = func(c *echo.Context, user string) (bool, error) {
			return config.Enforcer.Enforce(user, c.Request().URL.Path, c.Request().Method)
		}
	}

	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c *echo.Context) error {
			if config.Skipper(c) {
				return next(c)
			}

			user, err := config.UserGetter(c)
			if err != nil {
				return config.ErrorHandler(c, err, http.StatusForbidden)
			}
			pass, err := config.EnforceHandler(c, user)
			if err != nil {
				return config.ErrorHandler(c, err, http.StatusInternalServerError)
			}
			if !pass {
				return config.ErrorHandler(c, errors.New("enforce did not pass"), http.StatusForbidden)
			}
			return next(c)
		}
	}
}


================================================
FILE: casbin/casbin_test.go
================================================
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors

package casbin

import (
	"errors"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/casbin/casbin/v2"
	"github.com/labstack/echo/v5"
	"github.com/labstack/echo/v5/middleware"
)

func testRequest(t *testing.T, h echo.HandlerFunc, user string, path string, method string, code int) {
	e := echo.New()
	req := httptest.NewRequest(method, path, nil)
	req.SetBasicAuth(user, "secret")
	res := httptest.NewRecorder()
	c := e.NewContext(req, res)

	err := h(c)

	if err != nil {
		var errObj *echo.HTTPError
		if errors.As(err, &errObj) {
			if errObj.Code != code {
				t.Errorf("%s, %s, %s: %d, supposed to be %d", user, path, method, errObj.Code, code)
			}
		}
	} else {
		status := 0
		if eResp, uErr := echo.UnwrapResponse(c.Response()); uErr == nil {
			status = eResp.Status
		}
		if status != code {
			t.Errorf("%s, %s, %s: %d, supposed to be %d", user, path, method, status, code)
		}
	}
}

func TestAuth(t *testing.T) {
	ce, _ := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
	h := Middleware(ce)(func(c *echo.Context) error {
		return c.String(http.StatusOK, "test")
	})

	testRequest(t, h, "alice", "/dataset1/resource1", http.MethodGet, http.StatusOK)
	testRequest(t, h, "alice", "/dataset1/resource1", http.MethodPost, http.StatusOK)
	testRequest(t, h, "alice", "/dataset1/resource2", http.MethodGet, http.StatusOK)
	testRequest(t, h, "alice", "/dataset1/resource2", http.MethodPost, http.StatusForbidden)
}

func TestPathWildcard(t *testing.T) {
	ce, _ := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
	h := Middleware(ce)(func(c *echo.Context) error {
		return c.String(http.StatusOK, "test")
	})

	testRequest(t, h, "bob", "/dataset2/resource1", http.MethodGet, http.StatusOK)
	testRequest(t, h, "bob", "/dataset2/resource1", http.MethodPost, http.StatusOK)
	testRequest(t, h, "bob", "/dataset2/resource1", http.MethodDelete, http.StatusOK)
	testRequest(t, h, "bob", "/dataset2/resource2", http.MethodGet, http.StatusOK)
	testRequest(t, h, "bob", "/dataset2/resource2", http.MethodPost, http.StatusForbidden)
	testRequest(t, h, "bob", "/dataset2/resource2", http.MethodDelete, http.StatusForbidden)

	testRequest(t, h, "bob", "/dataset2/folder1/item1", http.MethodGet, http.StatusForbidden)
	testRequest(t, h, "bob", "/dataset2/folder1/item1", http.MethodPost, http.StatusOK)
	testRequest(t, h, "bob", "/dataset2/folder1/item1", http.MethodDelete, http.StatusForbidden)
	testRequest(t, h, "bob", "/dataset2/folder1/item2", http.MethodGet, http.StatusForbidden)
	testRequest(t, h, "bob", "/dataset2/folder1/item2", http.MethodPost, http.StatusOK)
	testRequest(t, h, "bob", "/dataset2/folder1/item2", http.MethodDelete, http.StatusForbidden)
}

func TestRBAC(t *testing.T) {
	ce, _ := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
	h := Middleware(ce)(func(c *echo.Context) error {
		return c.String(http.StatusOK, "test")
	})

	// cathy can access all /dataset1/* resources via all methods because it has the dataset1_admin role.
	testRequest(t, h, "cathy", "/dataset1/item", http.MethodGet, http.StatusOK)
	testRequest(t, h, "cathy", "/dataset1/item", http.MethodPost, http.StatusOK)
	testRequest(t, h, "cathy", "/dataset1/item", http.MethodDelete, http.StatusOK)
	testRequest(t, h, "cathy", "/dataset2/item", http.MethodGet, http.StatusForbidden)
	testRequest(t, h, "cathy", "/dataset2/item", http.MethodPost, http.StatusForbidden)
	testRequest(t, h, "cathy", "/dataset2/item", http.MethodDelete, http.StatusForbidden)

	// delete all roles on user cathy, so cathy cannot access any resources now.
	ce.DeleteRolesForUser("cathy")

	testRequest(t, h, "cathy", "/dataset1/item", http.MethodGet, http.StatusForbidden)
	testRequest(t, h, "cathy", "/dataset1/item", http.MethodPost, http.StatusForbidden)
	testRequest(t, h, "cathy", "/dataset1/item", http.MethodDelete, http.StatusForbidden)
	testRequest(t, h, "cathy", "/dataset2/item", http.MethodGet, http.StatusForbidden)
	testRequest(t, h, "cathy", "/dataset2/item", http.MethodPost, http.StatusForbidden)
	testRequest(t, h, "cathy", "/dataset2/item", http.MethodDelete, http.StatusForbidden)
}

func TestEnforceError(t *testing.T) {
	ce, _ := casbin.NewEnforcer("broken_auth_model.conf", "auth_policy.csv")
	h := Middleware(ce)(func(c *echo.Context) error {
		return c.String(http.StatusOK, "test")
	})

	testRequest(t, h, "cathy", "/dataset1/item", http.MethodGet, http.StatusInternalServerError)
}

func TestCustomUserGetter(t *testing.T) {
	ce, _ := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
	cnf := Config{
		Skipper:  middleware.DefaultSkipper,
		Enforcer: ce,
		UserGetter: func(c *echo.Context) (string, error) {
			return "not_cathy_at_all", nil
		},
	}
	h := MiddlewareWithConfig(cnf)(func(c *echo.Context) error {
		return c.String(http.StatusOK, "test")
	})
	testRequest(t, h, "cathy", "/dataset1/item", http.MethodGet, http.StatusForbidden)
}

func TestUserGetterError(t *testing.T) {
	ce, _ := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
	cnf := Config{
		Skipper:  middleware.DefaultSkipper,
		Enforcer: ce,
		UserGetter: func(c *echo.Context) (string, error) {
			return "", errors.New("no idea who you are")
		},
	}
	h := MiddlewareWithConfig(cnf)(func(c *echo.Context) error {
		return c.String(http.StatusOK, "test")
	})
	testRequest(t, h, "cathy", "/dataset1/item", http.MethodGet, http.StatusForbidden)
}

func TestCustomEnforceHandler(t *testing.T) {
	ce, err := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
	assert.NoError(t, err)

	_, err = ce.AddPolicy("bob", "/user/bob", "PATCH_SELF")
	assert.NoError(t, err)

	cnf := Config{
		EnforceHandler: func(c *echo.Context, user string) (bool, error) {
			method := c.Request().Method
			if strings.HasPrefix(c.Request().URL.Path, "/user/bob") {
				method += "_SELF"
			}
			return ce.Enforce(user, c.Request().URL.Path, method)
		},
	}
	h := MiddlewareWithConfig(cnf)(func(c *echo.Context) error {
		return c.String(http.StatusOK, "test")
	})
	testRequest(t, h, "bob", "/dataset2/resource1", http.MethodGet, http.StatusOK)
	testRequest(t, h, "bob", "/user/alice", http.MethodPatch, http.StatusForbidden)
	testRequest(t, h, "bob", "/user/bob", http.MethodPatch, http.StatusOK)
}

func TestCustomSkipper(t *testing.T) {
	ce, _ := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
	cnf := Config{
		Skipper: func(c *echo.Context) bool {
			return c.Request().URL.Path == "/dataset1/resource1"
		},
		Enforcer: ce,
	}
	h := MiddlewareWithConfig(cnf)(func(c *echo.Context) error {
		return c.String(http.StatusOK, "test")
	})
	testRequest(t, h, "alice", "/dataset1/resource1", http.MethodGet, http.StatusOK)
	testRequest(t, h, "alice", "/dataset1/resource2", http.MethodPost, http.StatusForbidden)
}


================================================
FILE: codecov.yml
================================================
coverage:
  status:
    project:
      default:
        threshold: 1%
    patch:
      default:
        threshold: 1%

comment:
  require_changes: true

================================================
FILE: echo.go
================================================
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors

package echo


================================================
FILE: echoprometheus/README.md
================================================
# Usage

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

```go
package main

import (
	"log/slog"

	"github.com/labstack/echo-contrib/v5/echoprometheus"
	"github.com/labstack/echo/v5"
)

func main() {
	e := echo.New()
	
	// Enable metrics middleware
	e.Use(echoprometheus.NewMiddleware("myapp"))
	e.GET("/metrics", echoprometheus.NewHandler())

	if err := e.Start(":1323"); err != nil {
		slog.Error("failed to start server", "error", err)
	}
}
```


================================================
FILE: echoprometheus/prometheus.go
================================================
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors

/*
Package echoprometheus provides middleware to add Prometheus metrics.

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
*/
package echoprometheus

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/labstack/echo-contrib/v5/internal/helpers"
	"github.com/labstack/echo/v5"
	"github.com/labstack/echo/v5/middleware"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/prometheus/common/expfmt"
)

const (
	defaultSubsystem = "echo"
)

const (
	_           = iota // ignore first value by assigning to blank identifier
	bKB float64 = 1 << (10 * iota)
	bMB
)

// sizeBuckets is the buckets for request/response size. Here we define a spectrum from 1KB through 1NB up to 10MB.
var 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}

// MiddlewareConfig contains the configuration for creating prometheus middleware collecting several default metrics.
type MiddlewareConfig struct {
	// Skipper defines a function to skip middleware.
	Skipper middleware.Skipper

	// Namespace is components of the fully-qualified name of the Metric (created by joining Namespace,Subsystem and Name components with "_")
	// Optional
	Namespace string

	// Subsystem is components of the fully-qualified name of the Metric (created by joining Namespace,Subsystem and Name components with "_")
	// Defaults to: "echo"
	Subsystem string

	// LabelFuncs allows adding custom labels in addition to default labels. When key has same name with default label
	// it replaces default one.
	LabelFuncs map[string]LabelValueFunc

	// HistogramOptsFunc allows to change options for metrics of type histogram before metric is registered to Registerer
	HistogramOptsFunc func(opts prometheus.HistogramOpts) prometheus.HistogramOpts

	// CounterOptsFunc allows to change options for metrics of type counter before metric is registered to Registerer
	CounterOptsFunc func(opts prometheus.CounterOpts) prometheus.CounterOpts

	// Registerer sets the prometheus.Registerer instance the middleware will register these metrics with.
	// Defaults to: prometheus.DefaultRegisterer
	Registerer prometheus.Registerer

	// BeforeNext is callback that is executed before next middleware/handler is called. Useful for case when you have own
	// metrics that need data to be stored for AfterNext.
	BeforeNext func(c *echo.Context)

	// AfterNext is callback that is executed after next middleware/handler returns. Useful for case when you have own
	// metrics that need incremented/observed.
	AfterNext func(c *echo.Context, err error)

	timeNow func() time.Time

	// If DoNotUseRequestPathFor404 is true, all 404 responses (due to non-matching route) will have the same `url` label and
	// thus won't generate new metrics.
	DoNotUseRequestPathFor404 bool

	// StatusCodeResolver resolves err & context into http status code. Default is to use context.Response().Status
	StatusCodeResolver func(c *echo.Context, err error) int
}

type LabelValueFunc func(c *echo.Context, err error) string

// HandlerConfig contains the configuration for creating HTTP handler for metrics.
type HandlerConfig struct {
	// Gatherer sets the prometheus.Gatherer instance the middleware will use when generating the metric endpoint handler.
	// Defaults to: prometheus.DefaultGatherer
	Gatherer prometheus.Gatherer
}

// PushGatewayConfig contains the configuration for pushing to a Prometheus push gateway.
type PushGatewayConfig struct {
	// PushGatewayURL is push gateway URL in format http://domain:port
	PushGatewayURL string

	// PushInterval in ticker interval for pushing gathered metrics to the Gateway
	// Defaults to: 1 minute
	PushInterval time.Duration

	// Gatherer sets the prometheus.Gatherer instance the middleware will use when generating the metric endpoint handler.
	// Defaults to: prometheus.DefaultGatherer
	Gatherer prometheus.Gatherer

	// ErrorHandler is function that is called when errors occur. When callback returns error StartPushGateway also returns.
	ErrorHandler func(err error) error

	// ClientTransport specifies the mechanism by which individual HTTP POST requests are made.
	// Defaults to: http.DefaultTransport
	ClientTransport http.RoundTripper
}

// NewHandler creates new instance of Handler using Prometheus default registry.
//
// 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
func NewHandler() echo.HandlerFunc {
	return NewHandlerWithConfig(HandlerConfig{})
}

// NewHandlerWithConfig creates new instance of Handler using given configuration.
func NewHandlerWithConfig(config HandlerConfig) echo.HandlerFunc {
	if config.Gatherer == nil {
		config.Gatherer = prometheus.DefaultGatherer
	}
	h := promhttp.HandlerFor(config.Gatherer, promhttp.HandlerOpts{DisableCompression: true})

	if r, ok := config.Gatherer.(prometheus.Registerer); ok {
		h = promhttp.InstrumentMetricHandler(r, h)
	}

	return func(c *echo.Context) error {
		h.ServeHTTP(c.Response(), c.Request())
		return nil
	}
}

// NewMiddleware creates new instance of middleware using Prometheus default registry.
//
// 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
func NewMiddleware(subsystem string) echo.MiddlewareFunc {
	return NewMiddlewareWithConfig(MiddlewareConfig{Subsystem: subsystem})
}

// NewMiddlewareWithConfig creates new instance of middleware using given configuration.
//
// Deprecated: use [echo-prometheus middleware](https://github.com/labstack/echo-prometheus) or [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters
func NewMiddlewareWithConfig(config MiddlewareConfig) echo.MiddlewareFunc {
	mw, err := config.ToMiddleware()
	if err != nil {
		panic(err)
	}
	return mw
}

// ToMiddleware converts configuration to middleware or returns an error.
//
// 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
func (conf MiddlewareConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
	if conf.timeNow == nil {
		conf.timeNow = time.Now
	}
	if conf.Subsystem == "" {
		conf.Subsystem = defaultSubsystem
	}
	if conf.Registerer == nil {
		conf.Registerer = prometheus.DefaultRegisterer
	}
	if conf.CounterOptsFunc == nil {
		conf.CounterOptsFunc = func(opts prometheus.CounterOpts) prometheus.CounterOpts {
			return opts
		}
	}
	if conf.HistogramOptsFunc == nil {
		conf.HistogramOptsFunc = func(opts prometheus.HistogramOpts) prometheus.HistogramOpts {
			return opts
		}
	}
	if conf.StatusCodeResolver == nil {
		conf.StatusCodeResolver = helpers.DefaultStatusResolver
	}

	labelNames, customValuers := createLabels(conf.LabelFuncs)

	requestCount := prometheus.NewCounterVec(
		conf.CounterOptsFunc(prometheus.CounterOpts{
			Namespace: conf.Namespace,
			Subsystem: conf.Subsystem,
			Name:      "requests_total",
			Help:      "How many HTTP requests processed, partitioned by status code and HTTP method.",
		}),
		labelNames,
	)
	// we do not allow skipping or replacing default collector but developer can use `conf.CounterOptsFunc` to rename
	// this middleware default collector, so they can have own collector with that same name.
	// and we treat all register errors as returnable failures
	if err := conf.Registerer.Register(requestCount); err != nil {
		return nil, err
	}

	requestDuration := prometheus.NewHistogramVec(
		conf.HistogramOptsFunc(prometheus.HistogramOpts{
			Namespace: conf.Namespace,
			Subsystem: conf.Subsystem,
			Name:      "request_duration_seconds",
			Help:      "The HTTP request latencies in seconds.",
			// 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}
			Buckets: prometheus.DefBuckets,
		}),
		labelNames,
	)
	if rErr := conf.Registerer.Register(requestDuration); rErr != nil {
		return nil, rErr
	}

	responseSize := prometheus.NewHistogramVec(
		conf.HistogramOptsFunc(prometheus.HistogramOpts{
			Namespace: conf.Namespace,
			Subsystem: conf.Subsystem,
			Name:      "response_size_bytes",
			Help:      "The HTTP response sizes in bytes.",
			Buckets:   sizeBuckets,
		}),
		labelNames,
	)
	if err := conf.Registerer.Register(responseSize); err != nil {
		return nil, err
	}

	requestSize := prometheus.NewHistogramVec(
		conf.HistogramOptsFunc(prometheus.HistogramOpts{
			Namespace: conf.Namespace,
			Subsystem: conf.Subsystem,
			Name:      "request_size_bytes",
			Help:      "The HTTP request sizes in bytes.",
			Buckets:   sizeBuckets,
		}),
		labelNames,
	)
	if rErr := conf.Registerer.Register(requestSize); rErr != nil {
		return nil, rErr
	}

	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c *echo.Context) error {
			// NB: we do not skip metrics handler path by default. This can be added with custom Skipper but for default
			// behaviour we measure metrics path request/response metrics also
			if conf.Skipper != nil && conf.Skipper(c) {
				return next(c)
			}

			if conf.BeforeNext != nil {
				conf.BeforeNext(c)
			}
			reqSz := computeApproximateRequestSize(c.Request())

			start := conf.timeNow()
			err := next(c)
			elapsed := float64(conf.timeNow().Sub(start)) / float64(time.Second)

			if conf.AfterNext != nil {
				conf.AfterNext(c, err)
			}

			url := c.Path() // contains route path ala `/users/:id`
			if url == "" && !conf.DoNotUseRequestPathFor404 {
				// as of Echo v4.10.1 path is empty for 404 cases (when router did not find any matching routes)
				// in this case we use actual path from request to have some distinction in Prometheus
				url = c.Request().URL.Path
			}

			status := conf.StatusCodeResolver(c, err)

			values := make([]string, len(labelNames))
			values[0] = strconv.Itoa(status)
			values[1] = c.Request().Method
			values[2] = c.Request().Host
			values[3] = strings.ToValidUTF8(url, "\uFFFD") // \uFFFD is � https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character
			for _, cv := range customValuers {
				values[cv.index] = cv.valueFunc(c, err)
			}
			if obs, err := requestDuration.GetMetricWithLabelValues(values...); err == nil {
				obs.Observe(elapsed)
			} else {
				return fmt.Errorf("failed to label request duration metric with values, err: %w", err)
			}
			if obs, err := requestCount.GetMetricWithLabelValues(values...); err == nil {
				obs.Inc()
			} else {
				return fmt.Errorf("failed to label request count metric with values, err: %w", err)
			}
			if obs, err := requestSize.GetMetricWithLabelValues(values...); err == nil {
				obs.Observe(float64(reqSz))
			} else {
				return fmt.Errorf("failed to label request size metric with values, err: %w", err)
			}
			if obs, err := responseSize.GetMetricWithLabelValues(values...); err == nil {
				if eResp, uErr := echo.UnwrapResponse(c.Response()); uErr == nil {
					obs.Observe(float64(eResp.Size))
				}
			} else {
				return fmt.Errorf("failed to label response size metric with values, err: %w", err)
			}

			return err
		}
	}, nil
}

type customLabelValuer struct {
	index     int
	label     string
	valueFunc LabelValueFunc
}

func createLabels(customLabelFuncs map[string]LabelValueFunc) ([]string, []customLabelValuer) {
	labelNames := []string{"code", "method", "host", "url"}
	if len(customLabelFuncs) == 0 {
		return labelNames, nil
	}

	customValuers := make([]customLabelValuer, 0)
	// we create valuers in two passes for a reason - first to get fixed order, and then we know to assign correct indexes
	for label, labelFunc := range customLabelFuncs {
		customValuers = append(customValuers, customLabelValuer{
			label:     label,
			valueFunc: labelFunc,
		})
	}
	sort.Slice(customValuers, func(i, j int) bool {
		return customValuers[i].label < customValuers[j].label
	})

	for cvIdx, cv := range customValuers {
		idx := containsAt(labelNames, cv.label)
		if idx == -1 {
			idx = len(labelNames)
			labelNames = append(labelNames, cv.label)
		}
		customValuers[cvIdx].index = idx
	}
	return labelNames, customValuers
}

func containsAt[K comparable](haystack []K, needle K) int {
	for i, v := range haystack {
		if v == needle {
			return i
		}
	}
	return -1
}

func computeApproximateRequestSize(r *http.Request) int {
	s := 0
	if r.URL != nil {
		s = len(r.URL.Path)
	}

	s += len(r.Method)
	s += len(r.Proto)
	for name, values := range r.Header {
		s += len(name)
		for _, value := range values {
			s += len(value)
		}
	}
	s += len(r.Host)

	// N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.

	if r.ContentLength != -1 {
		s += int(r.ContentLength)
	}
	return s
}

// RunPushGatewayGatherer starts pushing collected metrics and waits for it context to complete or ErrorHandler to return error.
//
// Example:
// ```
//
//	go func() {
//		config := echoprometheus.PushGatewayConfig{
//			PushGatewayURL: "https://host:9080",
//			PushInterval:   10 * time.Millisecond,
//		}
//		if err := echoprometheus.RunPushGatewayGatherer(context.Background(), config); !errors.Is(err, context.Canceled) {
//			log.Fatal(err)
//		}
//	}()
//
// ```
//
// 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
func RunPushGatewayGatherer(ctx context.Context, config PushGatewayConfig) error {
	if config.PushGatewayURL == "" {
		return errors.New("push gateway URL is missing")
	}
	if config.PushInterval <= 0 {
		config.PushInterval = 1 * time.Minute
	}
	if config.Gatherer == nil {
		config.Gatherer = prometheus.DefaultGatherer
	}
	if config.ErrorHandler == nil {
		config.ErrorHandler = func(err error) error {
			return nil
		}
	}

	client := &http.Client{
		Transport: config.ClientTransport,
	}
	out := &bytes.Buffer{}

	ticker := time.NewTicker(config.PushInterval)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			out.Reset()
			err := WriteGatheredMetrics(out, config.Gatherer)
			if err != nil {
				if hErr := config.ErrorHandler(fmt.Errorf("failed to create metrics: %w", err)); hErr != nil {
					return hErr
				}
				continue
			}
			req, err := http.NewRequestWithContext(ctx, http.MethodPost, config.PushGatewayURL, out)
			if err != nil {
				if hErr := config.ErrorHandler(fmt.Errorf("failed to create push gateway request: %w", err)); hErr != nil {
					return hErr
				}
				continue
			}
			res, err := client.Do(req)
			if err != nil {
				if hErr := config.ErrorHandler(fmt.Errorf("error sending to push gateway: %w", err)); hErr != nil {
					return hErr
				}
			}
			if res.StatusCode != http.StatusOK {
				if hErr := config.ErrorHandler(echo.NewHTTPError(res.StatusCode, "post metrics request did not succeed")); hErr != nil {
					return hErr
				}
			}
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}

// WriteGatheredMetrics gathers collected metrics and writes them to given writer
func WriteGatheredMetrics(writer io.Writer, gatherer prometheus.Gatherer) error {
	metricFamilies, err := gatherer.Gather()
	if err != nil {
		return err
	}
	for _, mf := range metricFamilies {
		if _, err := expfmt.MetricFamilyToText(writer, mf); err != nil {
			return err
		}
	}
	return nil
}


================================================
FILE: echoprometheus/prometheus_test.go
================================================
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors

package echoprometheus

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/labstack/echo/v5"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/stretchr/testify/assert"
)

func TestCustomRegistryMetrics(t *testing.T) {
	e := echo.New()

	customRegistry := prometheus.NewRegistry()
	e.Use(NewMiddlewareWithConfig(MiddlewareConfig{Registerer: customRegistry}))
	e.GET("/metrics", NewHandlerWithConfig(HandlerConfig{Gatherer: customRegistry}))

	assert.Equal(t, http.StatusNotFound, request(e, "/ping?test=1"))

	s, code := requestBody(e, "/metrics")
	assert.Equal(t, http.StatusOK, code)
	assert.Contains(t, s, `echo_request_duration_seconds_count{code="404",host="example.com",method="GET",url="/ping"} 1`)
}

func TestDefaultRegistryMetrics(t *testing.T) {
	e := echo.New()

	e.Use(NewMiddleware("myapp"))
	e.GET("/metrics", NewHandler())

	assert.Equal(t, http.StatusNotFound, request(e, "/ping?test=1"))

	s, code := requestBody(e, "/metrics")
	assert.Equal(t, http.StatusOK, code)
	assert.Contains(t, s, `myapp_request_duration_seconds_count{code="404",host="example.com",method="GET",url="/ping"} 1`)

	unregisterDefaults("myapp")
}

func TestPrometheus_Buckets(t *testing.T) {
	e := echo.New()

	customRegistry := prometheus.NewRegistry()
	e.Use(NewMiddlewareWithConfig(MiddlewareConfig{Registerer: customRegistry}))
	e.GET("/metrics", NewHandlerWithConfig(HandlerConfig{Gatherer: customRegistry}))

	assert.Equal(t, http.StatusNotFound, request(e, "/ping"))

	body, code := requestBody(e, "/metrics")
	assert.Equal(t, http.StatusOK, code)
	assert.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)")
	assert.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)")
	assert.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")
	assert.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)")
	assert.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")
	assert.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)")
}

func TestMiddlewareConfig_Skipper(t *testing.T) {
	e := echo.New()

	customRegistry := prometheus.NewRegistry()
	e.Use(NewMiddlewareWithConfig(MiddlewareConfig{
		Skipper: func(c *echo.Context) bool {
			hasSuffix := strings.HasSuffix(c.Path(), "ignore")
			return hasSuffix
		},
		Registerer: customRegistry,
	}))

	e.GET("/test", func(c *echo.Context) error {
		return c.String(http.StatusOK, "OK")
	})
	e.GET("/test_ignore", func(c *echo.Context) error {
		return c.String(http.StatusOK, "OK")
	})

	assert.Equal(t, http.StatusNotFound, request(e, "/ping"))
	assert.Equal(t, http.StatusOK, request(e, "/test"))
	assert.Equal(t, http.StatusOK, request(e, "/test_ignore"))

	out := &bytes.Buffer{}
	assert.NoError(t, WriteGatheredMetrics(out, customRegistry))

	body := out.String()
	assert.Contains(t, body, `echo_request_duration_seconds_count{code="200",host="example.com",method="GET",url="/test"} 1`)
	assert.Contains(t, body, `echo_request_duration_seconds_count{code="404",host="example.com",method="GET",url="/ping"} 1`)
	assert.Contains(t, body, `echo_request_duration_seconds_count{code="404",host="example.com",method="GET",url="/ping"} 1`)
	assert.NotContains(t, body, `test_ignore`) // because we skipped
}

func TestMetricsForErrors(t *testing.T) {
	e := echo.New()
	customRegistry := prometheus.NewRegistry()
	e.Use(NewMiddlewareWithConfig(MiddlewareConfig{
		Skipper: func(c *echo.Context) bool {
			return strings.HasSuffix(c.Path(), "ignore")
		},
		Subsystem:  "myapp",
		Registerer: customRegistry,
	}))
	e.GET("/metrics", NewHandlerWithConfig(HandlerConfig{Gatherer: customRegistry}))

	e.GET("/handler_for_ok", func(c *echo.Context) error {
		return c.JSON(http.StatusOK, "OK")
	})
	e.GET("/handler_for_nok", func(c *echo.Context) error {
		return c.JSON(http.StatusConflict, "NOK")
	})
	e.GET("/handler_for_error", func(c *echo.Context) error {
		return echo.NewHTTPError(http.StatusBadGateway, "BAD")
	})

	assert.Equal(t, http.StatusOK, request(e, "/handler_for_ok"))
	assert.Equal(t, http.StatusConflict, request(e, "/handler_for_nok"))
	assert.Equal(t, http.StatusConflict, request(e, "/handler_for_nok"))
	assert.Equal(t, http.StatusBadGateway, request(e, "/handler_for_error"))

	body, code := requestBody(e, "/metrics")
	assert.Equal(t, http.StatusOK, code)
	assert.Contains(t, body, fmt.Sprintf("%s_requests_total", "myapp"))
	assert.Contains(t, body, `myapp_requests_total{code="200",host="example.com",method="GET",url="/handler_for_ok"} 1`)
	assert.Contains(t, body, `myapp_requests_total{code="409",host="example.com",method="GET",url="/handler_for_nok"} 2`)
	assert.Contains(t, body, `myapp_requests_total{code="502",host="example.com",method="GET",url="/handler_for_error"} 1`)
}

func TestMiddlewareConfig_LabelFuncs(t *testing.T) {
	e := echo.New()
	customRegistry := prometheus.NewRegistry()
	e.Use(NewMiddlewareWithConfig(MiddlewareConfig{
		LabelFuncs: map[string]LabelValueFunc{
			"scheme": func(c *echo.Context, err error) string { // additional custom label
				return c.Scheme()
			},
			"method": func(c *echo.Context, err error) string { // overrides default 'method' label value
				return "overridden_" + c.Request().Method
			},
		},
		Registerer: customRegistry,
	}))
	e.GET("/metrics", NewHandlerWithConfig(HandlerConfig{Gatherer: customRegistry}))

	e.GET("/ok", func(c *echo.Context) error {
		return c.JSON(http.StatusOK, "OK")
	})

	assert.Equal(t, http.StatusOK, request(e, "/ok"))

	body, code := requestBody(e, "/metrics")
	assert.Equal(t, http.StatusOK, code)
	assert.Contains(t, body, `echo_request_duration_seconds_count{code="200",host="example.com",method="overridden_GET",scheme="http",url="/ok"} 1`)
}

func TestMiddlewareConfig_StatusCodeResolver(t *testing.T) {
	e := echo.New()
	customRegistry := prometheus.NewRegistry()
	customResolver := func(c *echo.Context, err error) int {
		if err == nil {
			if eResp, uErr := echo.UnwrapResponse(c.Response()); uErr == nil {
				return eResp.Status
			}
			return http.StatusOK
		}
		msg := err.Error()
		if strings.Contains(msg, "NOT FOUND") {
			return http.StatusNotFound
		}
		if strings.Contains(msg, "NOT Authorized") {
			return http.StatusUnauthorized
		}
		return http.StatusInternalServerError
	}
	e.Use(NewMiddlewareWithConfig(MiddlewareConfig{
		Skipper: func(c *echo.Context) bool {
			return strings.HasSuffix(c.Path(), "ignore")
		},
		Subsystem:          "myapp",
		Registerer:         customRegistry,
		StatusCodeResolver: customResolver,
	}))
	e.GET("/metrics", NewHandlerWithConfig(HandlerConfig{Gatherer: customRegistry}))

	e.GET("/handler_for_ok", func(c *echo.Context) error {
		return c.JSON(http.StatusOK, "OK")
	})
	e.GET("/handler_for_nok", func(c *echo.Context) error {
		return c.JSON(http.StatusConflict, "NOK")
	})
	e.GET("/handler_for_not_found", func(c *echo.Context) error {
		return errors.New("NOT FOUND")
	})
	e.GET("/handler_for_not_authorized", func(c *echo.Context) error {
		return errors.New("NOT Authorized")
	})
	e.GET("/handler_for_unknown_error", func(c *echo.Context) error {
		return errors.New("i do not know")
	})

	assert.Equal(t, http.StatusOK, request(e, "/handler_for_ok"))
	assert.Equal(t, http.StatusConflict, request(e, "/handler_for_nok"))
	assert.Equal(t, http.StatusInternalServerError, request(e, "/handler_for_not_found"))
	assert.Equal(t, http.StatusInternalServerError, request(e, "/handler_for_not_authorized"))
	assert.Equal(t, http.StatusInternalServerError, request(e, "/handler_for_unknown_error"))

	body, code := requestBody(e, "/metrics")
	assert.Equal(t, http.StatusOK, code)
	assert.Contains(t, body, fmt.Sprintf("%s_requests_total", "myapp"))
	assert.Contains(t, body, `myapp_requests_total{code="200",host="example.com",method="GET",url="/handler_for_ok"} 1`)
	assert.Contains(t, body, `myapp_requests_total{code="409",host="example.com",method="GET",url="/handler_for_nok"} 1`)
	assert.Contains(t, body, `myapp_requests_total{code="404",host="example.com",method="GET",url="/handler_for_not_found"} 1`)
	assert.Contains(t, body, `myapp_requests_total{code="401",host="example.com",method="GET",url="/handler_for_not_authorized"} 1`)
	assert.Contains(t, body, `myapp_requests_total{code="500",host="example.com",method="GET",url="/handler_for_unknown_error"} 1`)
}

func TestMiddlewareConfig_HistogramOptsFunc(t *testing.T) {
	e := echo.New()
	customRegistry := prometheus.NewRegistry()
	e.Use(NewMiddlewareWithConfig(MiddlewareConfig{
		HistogramOptsFunc: func(opts prometheus.HistogramOpts) prometheus.HistogramOpts {
			if opts.Name == "request_duration_seconds" {
				opts.ConstLabels = prometheus.Labels{"my_const": "123"}
			}
			return opts
		},
		Registerer: customRegistry,
	}))
	e.GET("/metrics", NewHandlerWithConfig(HandlerConfig{Gatherer: customRegistry}))

	e.GET("/ok", func(c *echo.Context) error {
		return c.JSON(http.StatusOK, "OK")
	})

	assert.Equal(t, http.StatusOK, request(e, "/ok"))

	body, code := requestBody(e, "/metrics")
	assert.Equal(t, http.StatusOK, code)

	// has const label
	assert.Contains(t, body, `echo_request_duration_seconds_count{code="200",host="example.com",method="GET",my_const="123",url="/ok"} 1`)
	// does not have const label
	assert.Contains(t, body, `echo_request_size_bytes_count{code="200",host="example.com",method="GET",url="/ok"} 1`)
}

func TestMiddlewareConfig_CounterOptsFunc(t *testing.T) {
	e := echo.New()
	customRegistry := prometheus.NewRegistry()
	e.Use(NewMiddlewareWithConfig(MiddlewareConfig{
		CounterOptsFunc: func(opts prometheus.CounterOpts) prometheus.CounterOpts {
			if opts.Name == "requests_total" {
				opts.ConstLabels = prometheus.Labels{"my_const": "123"}
			}
			return opts
		},
		Registerer: customRegistry,
	}))
	e.GET("/metrics", NewHandlerWithConfig(HandlerConfig{Gatherer: customRegistry}))

	e.GET("/ok", func(c *echo.Context) error {
		return c.JSON(http.StatusOK, "OK")
	})

	assert.Equal(t, http.StatusOK, request(e, "/ok"))

	body, code := requestBody(e, "/metrics")
	assert.Equal(t, http.StatusOK, code)

	// has const label
	assert.Contains(t, body, `echo_requests_total{code="200",host="example.com",method="GET",my_const="123",url="/ok"} 1`)
	// does not have const label
	assert.Contains(t, body, `echo_request_size_bytes_count{code="200",host="example.com",method="GET",url="/ok"} 1`)
}

func TestMiddlewareConfig_AfterNextFuncs(t *testing.T) {
	e := echo.New()

	customRegistry := prometheus.NewRegistry()
	customCounter := prometheus.NewCounter(
		prometheus.CounterOpts{
			Name: "custom_requests_total",
			Help: "How many HTTP requests processed, partitioned by status code and HTTP method.",
		},
	)
	if err := customRegistry.Register(customCounter); err != nil {
		t.Fatal(err)
	}

	e.Use(NewMiddlewareWithConfig(MiddlewareConfig{
		AfterNext: func(c *echo.Context, err error) {
			customCounter.Inc() // use our custom metric in middleware
		},
		Registerer: customRegistry,
	}))
	e.GET("/metrics", NewHandlerWithConfig(HandlerConfig{Gatherer: customRegistry}))

	e.GET("/ok", func(c *echo.Context) error {
		return c.JSON(http.StatusOK, "OK")
	})

	assert.Equal(t, http.StatusOK, request(e, "/ok"))

	body, code := requestBody(e, "/metrics")
	assert.Equal(t, http.StatusOK, code)
	assert.Contains(t, body, `custom_requests_total 1`)
}

func TestRunPushGatewayGatherer(t *testing.T) {
	receivedMetrics := false
	svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		receivedMetrics = true
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("OK"))
	}))
	defer svr.Close()

	ctx, cancel := context.WithTimeout(context.Background(), 250*time.Millisecond)
	defer cancel()

	config := PushGatewayConfig{
		PushGatewayURL: svr.URL,
		PushInterval:   10 * time.Millisecond,
		ErrorHandler: func(err error) error {
			return err // to force return after first request
		},
	}
	err := RunPushGatewayGatherer(ctx, config)

	assert.EqualError(t, err, "code=400, message=post metrics request did not succeed")
	assert.True(t, receivedMetrics)
	unregisterDefaults("myapp")
}

// TestSetPathFor404NoMatchingRoute tests that the url is not included in the metric when
// the 404 response is due to no matching route
func TestSetPathFor404NoMatchingRoute(t *testing.T) {
	e := echo.New()

	e.Use(NewMiddlewareWithConfig(MiddlewareConfig{DoNotUseRequestPathFor404: true, Subsystem: defaultSubsystem}))
	e.GET("/metrics", NewHandler())

	assert.Equal(t, http.StatusNotFound, request(e, "/nonExistentPath"))

	s, code := requestBody(e, "/metrics")
	assert.Equal(t, http.StatusOK, code)
	assert.Contains(t, s, fmt.Sprintf(`%s_request_duration_seconds_count{code="404",host="example.com",method="GET",url=""} 1`, defaultSubsystem))
	assert.NotContains(t, s, fmt.Sprintf(`%s_request_duration_seconds_count{code="404",host="example.com",method="GET",url="/nonExistentPath"} 1`, defaultSubsystem))

	unregisterDefaults(defaultSubsystem)
}

// TestSetPathFor404Logic tests that the url is included in the metric when the 404 response is due to logic
func TestSetPathFor404Logic(t *testing.T) {
	unregisterDefaults("myapp")
	e := echo.New()

	e.Use(NewMiddlewareWithConfig(MiddlewareConfig{DoNotUseRequestPathFor404: true, Subsystem: defaultSubsystem}))
	e.GET("/metrics", NewHandler())

	e.GET("/sample", func(c *echo.Context) error {
		return echo.ErrNotFound
	})

	assert.Equal(t, http.StatusNotFound, request(e, "/sample"))

	s, code := requestBody(e, "/metrics")
	assert.Equal(t, http.StatusOK, code)
	assert.NotContains(t, s, fmt.Sprintf(`%s_request_duration_seconds_count{code="404",host="example.com",method="GET",url=""} 1`, defaultSubsystem))
	assert.Contains(t, s, fmt.Sprintf(`%s_request_duration_seconds_count{code="404",host="example.com",method="GET",url="/sample"} 1`, defaultSubsystem))

	unregisterDefaults(defaultSubsystem)
}

func TestInvalidUTF8PathIsFixed(t *testing.T) {
	e := echo.New()

	e.Use(NewMiddlewareWithConfig(MiddlewareConfig{Subsystem: defaultSubsystem}))
	e.GET("/metrics", NewHandler())

	assert.Equal(t, http.StatusNotFound, request(e, "/../../WEB-INF/web.xml\xc0\x80.jsp"))

	s, code := requestBody(e, "/metrics")
	assert.Equal(t, http.StatusOK, code)
	assert.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))

	unregisterDefaults(defaultSubsystem)
}

func requestBody(e *echo.Echo, path string) (string, int) {
	req := httptest.NewRequest(http.MethodGet, path, nil)
	rec := httptest.NewRecorder()
	e.ServeHTTP(rec, req)

	return rec.Body.String(), rec.Code
}

func request(e *echo.Echo, path string) int {
	_, code := requestBody(e, path)
	return code
}

func unregisterDefaults(subsystem string) {
	// this is extremely hacky way to unregister our middleware metrics that it registers to prometheus default registry
	// Metrics/collector can be unregistered only by their instance but we do not have their instance, so we need to
	// create similar collector to register it and get error back with that existing collector we actually want to
	// unregister
	p := prometheus.DefaultRegisterer

	unRegisterCollector := func(opts prometheus.Opts) {
		dummyDuplicate := prometheus.NewCounterVec(prometheus.CounterOpts(opts), []string{"code", "method", "host", "url"})
		err := p.Register(dummyDuplicate)
		if err == nil {
			return
		}
		var arErr prometheus.AlreadyRegisteredError
		if errors.As(err, &arErr) {
			p.Unregister(arErr.ExistingCollector)
		}
	}

	unRegisterCollector(prometheus.Opts{
		Subsystem: subsystem,
		Name:      "requests_total",
		Help:      "How many HTTP requests processed, partitioned by status code and HTTP method.",
	})
	unRegisterCollector(prometheus.Opts{
		Subsystem: subsystem,
		Name:      "request_duration_seconds",
		Help:      "The HTTP request latencies in seconds.",
	})
	unRegisterCollector(prometheus.Opts{
		Subsystem: subsystem,
		Name:      "response_size_bytes",
		Help:      "The HTTP response sizes in bytes.",
	})
	unRegisterCollector(prometheus.Opts{
		Subsystem: subsystem,
		Name:      "request_size_bytes",
		Help:      "The HTTP request sizes in bytes.",
	})
}


================================================
FILE: go.mod
================================================
module github.com/labstack/echo-contrib/v5

go 1.25.0

require (
	github.com/casbin/casbin/v2 v2.135.0
	github.com/gorilla/sessions v1.4.0
	github.com/labstack/echo/v5 v5.0.4
	github.com/opentracing/opentracing-go v1.2.0
	github.com/openzipkin/zipkin-go v0.4.3
	github.com/prometheus/client_golang v1.23.2
	github.com/prometheus/common v0.67.5
	github.com/stretchr/testify v1.11.1
	github.com/uber/jaeger-client-go v2.30.0+incompatible
)

require (
	github.com/HdrHistogram/hdrhistogram-go v1.2.0 // indirect
	github.com/beorn7/perks v1.0.1 // indirect
	github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
	github.com/casbin/govaluate v1.10.0 // indirect
	github.com/cespare/xxhash/v2 v2.3.0 // indirect
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/google/uuid v1.6.0 // indirect
	github.com/gorilla/securecookie v1.1.2 // indirect
	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
	github.com/pkg/errors v0.9.1 // indirect
	github.com/pmezard/go-difflib v1.0.0 // indirect
	github.com/prometheus/client_model v0.6.2 // indirect
	github.com/prometheus/procfs v0.20.1 // indirect
	github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
	go.uber.org/atomic v1.11.0 // indirect
	go.yaml.in/yaml/v2 v2.4.4 // indirect
	golang.org/x/sys v0.42.0 // indirect
	golang.org/x/time v0.15.0 // indirect
	google.golang.org/grpc v1.79.3 // indirect
	google.golang.org/protobuf v1.36.11 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
)


================================================
FILE: go.sum
================================================
github.com/HdrHistogram/hdrhistogram-go v1.2.0 h1:XMJkDWuz6bM9Fzy7zORuVFKH7ZJY41G2q8KWhVGkNiY=
github.com/HdrHistogram/hdrhistogram-go v1.2.0/go.mod h1:CiIeGiHSd06zjX+FypuEJ5EQ07KKtxZ+8J6hszwVQig=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/casbin/casbin/v2 v2.135.0 h1:6BLkMQiGotYyS5yYeWgW19vxqugUlvHFkFiLnLR/bxk=
github.com/casbin/casbin/v2 v2.135.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/casbin/govaluate v1.10.0 h1:ffGw51/hYH3w3rZcxO/KcaUIDOLP84w7nsidMVgaDG0=
github.com/casbin/govaluate v1.10.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo/v5 v5.0.4 h1:ll3I/O8BifjMztj9dD1vx/peZQv8cR2CTUdQK6QxGGc=
github.com/labstack/echo/v5 v5.0.4/go.mod h1:SyvlSdObGjRXeQfCCXW/sybkZdOOQZBmpKF0bvALaeo=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: internal/helpers/statuscode.go
================================================
package helpers

import (
	"errors"
	"net/http"

	"github.com/labstack/echo/v5"
)

// DefaultStatusResolver resolves http status code from given err or Response.
func DefaultStatusResolver(c *echo.Context, err error) int {
	status := 0
	var sc echo.HTTPStatusCoder
	if errors.As(err, &sc) {
		return sc.StatusCode()
	}
	if eResp, uErr := echo.UnwrapResponse(c.Response()); uErr == nil {
		if eResp.Committed {
			status = eResp.Status
		}
	}
	if err != nil && status == 0 {
		status = http.StatusInternalServerError
	}
	return status
}


================================================
FILE: jaegertracing/jaegertracing.go
================================================
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors

/*
Package jaegertracing provides middleware to Opentracing using Jaeger.

Example:
```
package main
import (

	"github.com/labstack/echo-contrib/v5/jaegertracing"
	"github.com/labstack/echo/v5"

)

	func main() {
	    e := echo.New()
	    // Enable tracing middleware
	    c := jaegertracing.New(e, nil)
	    defer c.Close()

	    e.Logger.Fatal(e.Start(":1323"))
	}

```

Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/jaegertracing/jaeger-client-go
*/
package jaegertracing

import (
	"bytes"
	"crypto/rand"
	"errors"
	"fmt"
	"io"
	"net/http"
	"reflect"
	"runtime"
	"time"

	"github.com/labstack/echo-contrib/v5/internal/helpers"
	"github.com/labstack/echo/v5"
	"github.com/labstack/echo/v5/middleware"
	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/ext"
	"github.com/uber/jaeger-client-go/config"
)

const defaultComponentName = "echo/v5"

type (
	// TraceConfig defines the config for Trace middleware.
	TraceConfig struct {
		// Skipper defines a function to skip middleware.
		Skipper middleware.Skipper

		// OpenTracing Tracer instance which should be got before
		Tracer opentracing.Tracer

		// ComponentName used for describing the tracing component name
		ComponentName string

		// add req body & resp body to tracing tags
		IsBodyDump bool

		// prevent logging long http request bodies
		LimitHTTPBody bool

		// http body limit size (in bytes)
		// NOTE: don't specify values larger than 60000 as jaeger can't handle values in span.LogKV larger than 60000 bytes
		LimitSize int

		// OperationNameFunc composes operation name based on context. Can be used to override default naming
		OperationNameFunc func(c *echo.Context) string
	}
)

var (
	// DefaultTraceConfig is the default Trace middleware config.
	DefaultTraceConfig = TraceConfig{
		Skipper:       middleware.DefaultSkipper,
		ComponentName: defaultComponentName,
		IsBodyDump:    false,

		LimitHTTPBody:     true,
		LimitSize:         60_000,
		OperationNameFunc: defaultOperationName,
	}
)

// New creates an Opentracing tracer and attaches it to Echo middleware.
// Returns Closer do be added to caller function as `defer closer.Close()`
//
// Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/jaegertracing/jaeger-client-go
func New(e *echo.Echo, skipper middleware.Skipper) io.Closer {
	// Add Opentracing instrumentation
	defcfg := config.Configuration{
		ServiceName: "echo-tracer",
		Sampler: &config.SamplerConfig{
			Type:  "const",
			Param: 1,
		},
		Reporter: &config.ReporterConfig{
			LogSpans:            true,
			BufferFlushInterval: 1 * time.Second,
		},
	}
	cfg, err := defcfg.FromEnv()
	if err != nil {
		panic("Could not parse Jaeger env vars: " + err.Error())
	}
	tracer, closer, err := cfg.NewTracer()
	if err != nil {
		panic("Could not initialize jaeger tracer: " + err.Error())
	}

	opentracing.SetGlobalTracer(tracer)
	e.Use(TraceWithConfig(TraceConfig{
		Tracer:  tracer,
		Skipper: skipper,
	}))
	return closer
}

// Trace returns a Trace middleware.
// Trace middleware traces http requests and reporting errors.
//
// Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/jaegertracing/jaeger-client-go
func Trace(tracer opentracing.Tracer) echo.MiddlewareFunc {
	c := DefaultTraceConfig
	c.Tracer = tracer
	c.ComponentName = defaultComponentName
	return TraceWithConfig(c)
}

// TraceWithConfig returns a Trace middleware with config.
// See: `Trace()`.
//
// Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/jaegertracing/jaeger-client-go
func TraceWithConfig(config TraceConfig) echo.MiddlewareFunc {
	if config.Tracer == nil {
		panic("echo: trace middleware requires opentracing tracer")
	}
	if config.Skipper == nil {
		config.Skipper = middleware.DefaultSkipper
	}
	if config.ComponentName == "" {
		config.ComponentName = defaultComponentName
	}
	if config.OperationNameFunc == nil {
		config.OperationNameFunc = defaultOperationName
	}

	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c *echo.Context) error {
			if config.Skipper(c) {
				return next(c)
			}

			req := c.Request()
			opname := config.OperationNameFunc(c)
			realIP := c.RealIP()
			requestID := getRequestID(c) // request-id generated by reverse-proxy

			var sp opentracing.Span
			var err error

			ctx, err := config.Tracer.Extract(
				opentracing.HTTPHeaders,
				opentracing.HTTPHeadersCarrier(req.Header),
			)

			if err != nil {
				sp = config.Tracer.StartSpan(opname)
			} else {
				sp = config.Tracer.StartSpan(opname, ext.RPCServerOption(ctx))
			}
			defer sp.Finish()

			ext.HTTPMethod.Set(sp, req.Method)
			ext.HTTPUrl.Set(sp, req.URL.String())
			ext.Component.Set(sp, config.ComponentName)
			sp.SetTag("client_ip", realIP)
			sp.SetTag("request_id", requestID)

			// Dump request & response body
			var respDumper *responseDumper
			if config.IsBodyDump {
				// request
				reqBody := []byte{}
				if c.Request().Body != nil {
					reqBody, _ = io.ReadAll(c.Request().Body)

					if config.LimitHTTPBody {
						sp.LogKV("http.req.body", limitString(string(reqBody), config.LimitSize))
					} else {
						sp.LogKV("http.req.body", string(reqBody))
					}
				}

				req.Body = io.NopCloser(bytes.NewBuffer(reqBody)) // reset original request body

				// response
				respDumper = newResponseDumper(c.Response())
				c.SetResponse(respDumper)
			}

			// setup request context - add opentracing span
			reqSpan := req.WithContext(opentracing.ContextWithSpan(req.Context(), sp))
			c.SetRequest(reqSpan)
			defer func() {
				// as we have created new http.Request object we need to make sure that temporary files created to hold MultipartForm
				// files are cleaned up. This is done by http.Server at the end of request lifecycle but Server does not
				// have reference to our new Request instance therefore it is our responsibility to fix the mess we caused.
				//
				// This means that when we are on returning path from handler middlewares up in chain from this middleware
				// can not access these temporary files anymore because we deleted them here.
				if reqSpan.MultipartForm != nil {
					reqSpan.MultipartForm.RemoveAll()
				}
			}()

			// inject Jaeger context into request header
			config.Tracer.Inject(sp.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(c.Request().Header))

			// call next middleware / controller
			err = next(c)

			status := helpers.DefaultStatusResolver(c, err)
			ext.HTTPStatusCode.Set(sp, uint16(status))

			if err != nil {
				logError(sp, err)
			}

			// Dump response body
			if config.IsBodyDump {
				if config.LimitHTTPBody {
					sp.LogKV("http.resp.body", limitString(respDumper.GetResponse(), config.LimitSize))
				} else {
					sp.LogKV("http.resp.body", respDumper.GetResponse())
				}
			}

			return nil // error was already processed with ctx.Error(err)
		}
	}
}

func limitString(str string, size int) string {
	if len(str) > size {
		return str[:size/2] + "\n---- skipped ----\n" + str[len(str)-size/2:]
	}

	return str
}

func logError(span opentracing.Span, err error) {
	var httpError *echo.HTTPError
	if errors.As(err, &httpError) {
		span.LogKV("error.message", httpError.Message)
	} else {
		span.LogKV("error.message", err.Error())
	}
	span.SetTag("error", true)
}

func getRequestID(ctx *echo.Context) string {
	requestID := ctx.Request().Header.Get(echo.HeaderXRequestID) // request-id generated by reverse-proxy
	if requestID == "" {
		requestID = generateToken() // missed request-id from proxy, we generate it manually
	}
	return requestID
}

func generateToken() string {
	b := make([]byte, 16)
	rand.Read(b)
	return fmt.Sprintf("%x", b)
}

func defaultOperationName(c *echo.Context) string {
	req := c.Request()
	return "HTTP " + req.Method + " URL: " + c.Path()
}

// TraceFunction wraps funtion with opentracing span adding tags for the function name and caller details
//
// Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/jaegertracing/jaeger-client-go
func TraceFunction(ctx *echo.Context, fn interface{}, params ...interface{}) (result []reflect.Value) {
	// Get function name
	name := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
	// Create child span
	parentSpan := opentracing.SpanFromContext(ctx.Request().Context())
	sp := opentracing.StartSpan(
		"Function - "+name,
		opentracing.ChildOf(parentSpan.Context()))
	defer sp.Finish()

	sp.SetTag("function", name)

	// Get caller function name, file and line
	pc := make([]uintptr, 15)
	n := runtime.Callers(2, pc)
	frames := runtime.CallersFrames(pc[:n])
	frame, _ := frames.Next()
	callerDetails := fmt.Sprintf("%s - %s#%d", frame.Function, frame.File, frame.Line)
	sp.SetTag("caller", callerDetails)

	// Check params and call function
	f := reflect.ValueOf(fn)
	if f.Type().NumIn() != len(params) {
		e := fmt.Sprintf("Incorrect number of parameters calling wrapped function %s", name)
		panic(e)
	}
	inputs := make([]reflect.Value, len(params))
	for k, in := range params {
		inputs[k] = reflect.ValueOf(in)
	}
	return f.Call(inputs)
}

// CreateChildSpan creates a new opentracing span adding tags for the span name and caller details.
// User must call defer `sp.Finish()`
//
// Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/jaegertracing/jaeger-client-go
func CreateChildSpan(ctx *echo.Context, name string) opentracing.Span {
	parentSpan := opentracing.SpanFromContext(ctx.Request().Context())
	sp := opentracing.StartSpan(
		name,
		opentracing.ChildOf(parentSpan.Context()))
	sp.SetTag("name", name)

	// Get caller function name, file and line
	pc := make([]uintptr, 15)
	n := runtime.Callers(2, pc)
	frames := runtime.CallersFrames(pc[:n])
	frame, _ := frames.Next()
	callerDetails := fmt.Sprintf("%s - %s#%d", frame.Function, frame.File, frame.Line)
	sp.SetTag("caller", callerDetails)

	return sp
}

// NewTracedRequest generates a new traced HTTP request with opentracing headers injected into it
//
// Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) instead + OTLP exporters. Read this: https://github.com/jaegertracing/jaeger-client-go
func NewTracedRequest(method string, url string, body io.Reader, span opentracing.Span) (*http.Request, error) {
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		panic(err.Error())
	}

	ext.SpanKindRPCClient.Set(span)
	ext.HTTPUrl.Set(span, url)
	ext.HTTPMethod.Set(span, method)
	span.Tracer().Inject(span.Context(),
		opentracing.HTTPHeaders,
		opentracing.HTTPHeadersCarrier(req.Header))

	return req, err
}


================================================
FILE: jaegertracing/jaegertracing_test.go
================================================
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors

package jaegertracing

import (
	"bytes"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/labstack/echo/v5"
	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/log"
	"github.com/stretchr/testify/assert"
)

// Mock opentracing.Span
type mockSpan struct {
	tracer   opentracing.Tracer
	tags     map[string]interface{}
	logs     map[string]interface{}
	opName   string
	finished bool
}

func createSpan(tracer opentracing.Tracer) *mockSpan {
	return &mockSpan{
		tracer: tracer,
		tags:   make(map[string]interface{}),
		logs:   make(map[string]interface{}),
	}
}

func (sp *mockSpan) isFinished() bool {
	return sp.finished
}

func (sp *mockSpan) getOpName() string {
	return sp.opName
}

func (sp *mockSpan) getTag(key string) interface{} {
	return sp.tags[key]
}

func (sp *mockSpan) getLog(key string) interface{} {
	return sp.logs[key]
}

func (sp *mockSpan) Finish() {
	sp.finished = true
}
func (sp *mockSpan) FinishWithOptions(opts opentracing.FinishOptions) {
}
func (sp *mockSpan) Context() opentracing.SpanContext {
	return nil
}
func (sp *mockSpan) SetOperationName(operationName string) opentracing.Span {
	sp.opName = operationName
	return sp
}
func (sp *mockSpan) SetTag(key string, value interface{}) opentracing.Span {
	sp.tags[key] = value
	return sp
}
func (sp *mockSpan) LogFields(fields ...log.Field) {
}
func (sp *mockSpan) LogKV(alternatingKeyValues ...interface{}) {
	for i := 0; i < len(alternatingKeyValues); i += 2 {
		ikey := alternatingKeyValues[i]
		value := alternatingKeyValues[i+1]
		if key, ok := ikey.(string); ok {
			sp.logs[key] = value
		}
	}
}
func (sp *mockSpan) SetBaggageItem(restrictedKey, value string) opentracing.Span {
	return sp
}
func (sp *mockSpan) BaggageItem(restrictedKey string) string {
	return ""
}
func (sp *mockSpan) Tracer() opentracing.Tracer {
	return sp.tracer
}
func (sp *mockSpan) LogEvent(event string) {
}
func (sp *mockSpan) LogEventWithPayload(event string, payload interface{}) {
}
func (sp *mockSpan) Log(data opentracing.LogData) {
}

// Mock opentracing.Tracer
type mockTracer struct {
	span                   *mockSpan
	hasStartSpanWithOption bool
}

func (tr *mockTracer) currentSpan() *mockSpan {
	return tr.span
}

func (tr *mockTracer) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span {
	tr.hasStartSpanWithOption = len(opts) > 0
	if tr.span != nil {
		tr.span.opName = operationName
		return tr.span
	}
	span := createSpan(tr)
	span.opName = operationName
	return span
}

func (tr *mockTracer) Inject(sm opentracing.SpanContext, format interface{}, carrier interface{}) error {
	return nil
}

func (tr *mockTracer) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) {
	if tr.span != nil {
		return nil, nil
	}
	return nil, errors.New("no span")
}

func createMockTracer() *mockTracer {
	tracer := mockTracer{}
	span := createSpan(&tracer)
	tracer.span = span
	return &tracer
}

func TestTraceWithDefaultConfig(t *testing.T) {
	tracer := createMockTracer()

	e := echo.New()
	e.Use(Trace(tracer))

	e.GET("/hello", func(c *echo.Context) error {
		return c.String(http.StatusOK, "world")
	})

	e.GET("/giveme400", func(c *echo.Context) error {
		return echo.NewHTTPError(http.StatusBadRequest, "baaaad request")
	})

	e.GET("/givemeerror", func(c *echo.Context) error {
		return fmt.Errorf("internal stuff went wrong")
	})

	t.Run("successful call", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/hello", nil)
		rec := httptest.NewRecorder()
		e.ServeHTTP(rec, req)

		assert.Equal(t, "GET", tracer.currentSpan().getTag("http.method"))
		assert.Equal(t, "/hello", tracer.currentSpan().getTag("http.url"))
		assert.Equal(t, defaultComponentName, tracer.currentSpan().getTag("component"))
		assert.Equal(t, uint16(200), tracer.currentSpan().getTag("http.status_code"))
		assert.NotEqual(t, true, tracer.currentSpan().getTag("error"))
	})

	t.Run("error from echo", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/idontexist", nil)
		rec := httptest.NewRecorder()
		e.ServeHTTP(rec, req)

		assert.Equal(t, "GET", tracer.currentSpan().getTag("http.method"))
		assert.Equal(t, "/idontexist", tracer.currentSpan().getTag("http.url"))
		assert.Equal(t, defaultComponentName, tracer.currentSpan().getTag("component"))
		assert.Equal(t, uint16(404), tracer.currentSpan().getTag("http.status_code"))
		assert.Equal(t, true, tracer.currentSpan().getTag("error"))
	})

	t.Run("custom http error", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/giveme400", nil)
		rec := httptest.NewRecorder()
		e.ServeHTTP(rec, req)

		assert.Equal(t, uint16(400), tracer.currentSpan().getTag("http.status_code"))
		assert.Equal(t, true, tracer.currentSpan().getTag("error"))
		assert.Equal(t, "baaaad request", tracer.currentSpan().getLog("error.message"))
	})

	t.Run("unknown error", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/givemeerror", nil)
		rec := httptest.NewRecorder()
		e.ServeHTTP(rec, req)

		assert.Equal(t, uint16(500), tracer.currentSpan().getTag("http.status_code"))
		assert.Equal(t, true, tracer.currentSpan().getTag("error"))
		assert.Equal(t, "internal stuff went wrong", tracer.currentSpan().getLog("error.message"))
	})
}

func TestTraceWithConfig(t *testing.T) {
	tracer := createMockTracer()

	e := echo.New()
	e.Use(TraceWithConfig(TraceConfig{
		Tracer:        tracer,
		ComponentName: "EchoTracer",
	}))
	req := httptest.NewRequest(http.MethodGet, "/trace", nil)
	rec := httptest.NewRecorder()
	e.ServeHTTP(rec, req)

	assert.Equal(t, true, tracer.currentSpan().isFinished())
	assert.Equal(t, "/trace", tracer.currentSpan().getTag("http.url"))
	assert.Equal(t, "EchoTracer", tracer.currentSpan().getTag("component"))
	assert.Equal(t, true, tracer.hasStartSpanWithOption)

}

func TestTraceWithConfigOfBodyDump(t *testing.T) {
	tracer := createMockTracer()

	e := echo.New()
	e.Use(TraceWithConfig(TraceConfig{
		Tracer:        tracer,
		ComponentName: "EchoTracer",
		IsBodyDump:    true,
	}))
	e.POST("/trace", func(c *echo.Context) error {
		return c.String(200, "Hi")
	})

	req := httptest.NewRequest(http.MethodPost, "/trace", bytes.NewBufferString(`{"name": "Lorem"}`))
	rec := httptest.NewRecorder()
	e.ServeHTTP(rec, req)

	assert.Equal(t, true, tracer.currentSpan().isFinished())
	assert.Equal(t, "EchoTracer", tracer.currentSpan().getTag("component"))
	assert.Equal(t, "/trace", tracer.currentSpan().getTag("http.url"))
	assert.Equal(t, `{"name": "Lorem"}`, tracer.currentSpan().getLog("http.req.body"))
	assert.Equal(t, `Hi`, tracer.currentSpan().getLog("http.resp.body"))
	assert.Equal(t, uint16(200), tracer.currentSpan().getTag("http.status_code"))
	assert.Equal(t, nil, tracer.currentSpan().getTag("error"))
	assert.Equal(t, true, tracer.hasStartSpanWithOption)

}

func TestTraceWithConfigOfNoneComponentName(t *testing.T) {
	tracer := createMockTracer()

	e := echo.New()
	e.Use(TraceWithConfig(TraceConfig{
		Tracer: tracer,
	}))
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	e.ServeHTTP(rec, req)

	assert.Equal(t, true, tracer.currentSpan().isFinished())
	assert.Equal(t, defaultComponentName, tracer.currentSpan().getTag("component"))
}

func TestTraceWithConfigOfSkip(t *testing.T) {
	tracer := createMockTracer()
	e := echo.New()
	e.Use(TraceWithConfig(TraceConfig{
		Skipper: func(*echo.Context) bool {
			return true
		},
		Tracer: tracer,
	}))
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	e.ServeHTTP(rec, req)

	assert.Equal(t, false, tracer.currentSpan().isFinished())
}

func TestTraceOfNoCurrentSpan(t *testing.T) {
	tracer := &mockTracer{}
	e := echo.New()
	e.Use(Trace(tracer))
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	e.ServeHTTP(rec, req)

	assert.Equal(t, false, tracer.hasStartSpanWithOption)
}

func TestTraceWithLimitHTTPBody(t *testing.T) {
	tracer := createMockTracer()

	e := echo.New()
	e.Use(TraceWithConfig(TraceConfig{
		Tracer:        tracer,
		ComponentName: "EchoTracer",
		IsBodyDump:    true,
		LimitHTTPBody: true,
		LimitSize:     10,
	}))
	e.POST("/trace", func(c *echo.Context) error {
		return c.String(200, "Hi 123456789012345678901234567890")
	})

	req := httptest.NewRequest(http.MethodPost, "/trace", bytes.NewBufferString("123456789012345678901234567890"))
	rec := httptest.NewRecorder()
	e.ServeHTTP(rec, req)

	assert.Equal(t, true, tracer.currentSpan().isFinished())
	assert.Equal(t, "12345\n---- skipped ----\n67890", tracer.currentSpan().getLog("http.req.body"))
	assert.Equal(t, "Hi 12\n---- skipped ----\n67890", tracer.currentSpan().getLog("http.resp.body"))
}

func TestTraceWithoutLimitHTTPBody(t *testing.T) {
	tracer := createMockTracer()

	e := echo.New()
	e.Use(TraceWithConfig(TraceConfig{
		Tracer:        tracer,
		ComponentName: "EchoTracer",
		IsBodyDump:    true,
		LimitHTTPBody: false, // disabled
		LimitSize:     10,
	}))
	e.POST("/trace", func(c *echo.Context) error {
		return c.String(200, "Hi 123456789012345678901234567890")
	})

	req := httptest.NewRequest(http.MethodPost, "/trace", bytes.NewBufferString("123456789012345678901234567890"))
	rec := httptest.NewRecorder()
	e.ServeHTTP(rec, req)

	assert.Equal(t, true, tracer.currentSpan().isFinished())
	assert.Equal(t, "123456789012345678901234567890", tracer.currentSpan().getLog("http.req.body"))
	assert.Equal(t, "Hi 123456789012345678901234567890", tracer.currentSpan().getLog("http.resp.body"))
}

func TestTraceWithDefaultOperationName(t *testing.T) {
	tracer := createMockTracer()

	e := echo.New()
	e.Use(Trace(tracer))

	e.GET("/trace", func(c *echo.Context) error {
		return c.String(http.StatusOK, "Hi")
	})

	req := httptest.NewRequest(http.MethodGet, "/trace", nil)
	rec := httptest.NewRecorder()
	e.ServeHTTP(rec, req)

	assert.Equal(t, "HTTP GET URL: /trace", tracer.currentSpan().getOpName())
}

func TestTraceWithCustomOperationName(t *testing.T) {
	tracer := createMockTracer()

	e := echo.New()
	e.Use(TraceWithConfig(TraceConfig{
		Tracer:        tracer,
		ComponentName: "EchoTracer",
		OperationNameFunc: func(c *echo.Context) string {
			// This is an example of operation name customization
			// In most cases default formatting is more than enough
			req := c.Request()
			opName := "HTTP " + req.Method

			path := c.Path()
			for _, pv := range c.PathValues() {
				from := ":" + pv.Name
				to := "{" + pv.Name + "}"
				path = strings.ReplaceAll(path, from, to)
			}

			return opName + " " + path
		},
	}))

	e.GET("/trace/:traceID/spans/:spanID", func(c *echo.Context) error {
		return c.String(http.StatusOK, "Hi")
	})

	req := httptest.NewRequest(http.MethodGet, "/trace/123456/spans/123", nil)
	rec := httptest.NewRecorder()
	e.ServeHTTP(rec, req)

	assert.Equal(t, true, tracer.currentSpan().isFinished())
	assert.Equal(t, "HTTP GET /trace/{traceID}/spans/{spanID}", tracer.currentSpan().getOpName())
}


================================================
FILE: jaegertracing/response_dumper.go
================================================
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors

package jaegertracing

import (
	"bytes"
	"io"
	"net/http"
)

type responseDumper struct {
	http.ResponseWriter

	mw  io.Writer
	buf *bytes.Buffer
}

func newResponseDumper(resp http.ResponseWriter) *responseDumper {
	buf := new(bytes.Buffer)
	return &responseDumper{
		ResponseWriter: resp,

		mw:  io.MultiWriter(resp, buf),
		buf: buf,
	}
}

func (d *responseDumper) Write(b []byte) (int, error) {
	return d.mw.Write(b)
}

func (d *responseDumper) GetResponse() string {
	return d.buf.String()
}

func (d *responseDumper) Unwrap() http.ResponseWriter {
	return d.ResponseWriter
}


================================================
FILE: pprof/README.md
================================================
Usage

```go
package main

import (
	"log/slog"

	"github.com/labstack/echo-contrib/v5/pprof"
	"github.com/labstack/echo/v5"
)

func main() {
	e := echo.New()
	pprof.Register(e)
	//......
	if err := e.Start(":1323"); err != nil {
		slog.Error("failed to start server", "error", err)
	}
}
```

- Then use the pprof tool to look at the heap profile:

    `go tool pprof http://localhost:1323/debug/pprof/heap`

-  Or to look at a 30-second CPU profile:
    
    `go tool pprof http://localhost:1323/debug/pprof/profile?seconds=30`

- Or to look at the goroutine blocking profile, after calling runtime.SetBlockProfileRate in your program:
    
    `go tool pprof http://localhost:1323/debug/pprof/block`

- Or to look at the holders of contended mutexes, after calling runtime.SetMutexProfileFraction in your program:
    
    `go tool pprof http://localhost:1323/debug/pprof/mutex`




================================================
FILE: pprof/pprof.go
================================================
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors

package pprof

import (
	"net/http"
	"net/http/pprof"

	"github.com/labstack/echo/v5"
)

const (
	// DefaultPrefix url prefix of pprof
	DefaultPrefix = "/debug/pprof"
)

func getPrefix(prefixOptions ...string) string {
	if len(prefixOptions) > 0 {
		return prefixOptions[0]
	}
	return DefaultPrefix
}

// Register middleware for net/http/pprof
func Register(e *echo.Echo, prefixOptions ...string) {
	prefix := getPrefix(prefixOptions...)

	prefixRouter := e.Group(prefix)
	{
		prefixRouter.GET("/", handler(pprof.Index))
		prefixRouter.GET("/allocs", handler(pprof.Handler("allocs").ServeHTTP))
		prefixRouter.GET("/block", handler(pprof.Handler("block").ServeHTTP))
		prefixRouter.GET("/cmdline", handler(pprof.Cmdline))
		prefixRouter.GET("/goroutine", handler(pprof.Handler("goroutine").ServeHTTP))
		prefixRouter.GET("/heap", handler(pprof.Handler("heap").ServeHTTP))
		prefixRouter.GET("/mutex", handler(pprof.Handler("mutex").ServeHTTP))
		prefixRouter.GET("/profile", handler(pprof.Profile))
		prefixRouter.POST("/symbol", handler(pprof.Symbol))
		prefixRouter.GET("/symbol", handler(pprof.Symbol))
		prefixRouter.GET("/threadcreate", handler(pprof.Handler("threadcreate").ServeHTTP))
		prefixRouter.GET("/trace", handler(pprof.Trace))
	}
}

func handler(h http.HandlerFunc) echo.HandlerFunc {
	return func(c *echo.Context) error {
		h.ServeHTTP(c.Response(), c.Request())
		return nil
	}
}


================================================
FILE: pprof/pprof_test.go
================================================
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors

package pprof

import (
	"github.com/stretchr/testify/assert"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/labstack/echo/v5"
)

func TestPProfRegisterDefaualtPrefix(t *testing.T) {
	var pprofPaths = []struct {
		path string
	}{
		{"/"},
		{"/allocs"},
		{"/block"},
		{"/cmdline"},
		{"/goroutine"},
		{"/heap"},
		{"/mutex"},
		{"/profile?seconds=1"},
		{"/symbol"},
		{"/symbol"},
		{"/threadcreate"},
		{"/trace"},
	}
	for _, tt := range pprofPaths {
		t.Run(tt.path, func(t *testing.T) {
			e := echo.New()
			Register(e)
			req, _ := http.NewRequest(http.MethodGet, DefaultPrefix+tt.path, nil)
			rec := httptest.NewRecorder()
			e.ServeHTTP(rec, req)
			assert.Equal(t, rec.Code, http.StatusOK)
		})
	}
}

func TestPProfRegisterCustomPrefix(t *testing.T) {
	var pprofPaths = []struct {
		path string
	}{
		{"/"},
		{"/allocs"},
		{"/block"},
		{"/cmdline"},
		{"/goroutine"},
		{"/heap"},
		{"/mutex"},
		{"/profile?seconds=1"},
		{"/symbol"},
		{"/symbol"},
		{"/threadcreate"},
		{"/trace"},
	}
	for _, tt := range pprofPaths {
		t.Run(tt.path, func(t *testing.T) {
			e := echo.New()
			pprofPrefix := "/myapp/pprof"
			Register(e, pprofPrefix)
			req, _ := http.NewRequest(http.MethodGet, pprofPrefix+tt.path, nil)
			rec := httptest.NewRecorder()
			e.ServeHTTP(rec, req)
			assert.Equal(t, rec.Code, http.StatusOK)
		})
	}
}


================================================
FILE: session/session.go
================================================
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors

package session

import (
	"fmt"

	"github.com/gorilla/sessions"
	"github.com/labstack/echo/v5"
	"github.com/labstack/echo/v5/middleware"
)

type (
	// Config defines the config for Session middleware.
	Config struct {
		// Skipper defines a function to skip middleware.
		Skipper middleware.Skipper

		// Session store.
		// Required.
		Store sessions.Store
	}
)

const (
	key = "_session_store"
)

var (
	// DefaultConfig is the default Session middleware config.
	DefaultConfig = Config{
		Skipper: middleware.DefaultSkipper,
	}
)

// Get returns a named session.
func Get(name string, c *echo.Context) (*sessions.Session, error) {
	s := c.Get(key)
	if s == nil {
		return nil, fmt.Errorf("%q session store not found", key)
	}
	store := s.(sessions.Store)
	return store.Get(c.Request(), name)
}

// Middleware returns a Session middleware.
func Middleware(store sessions.Store) echo.MiddlewareFunc {
	c := DefaultConfig
	c.Store = store
	return MiddlewareWithConfig(c)
}

// MiddlewareWithConfig returns a Sessions middleware with config.
// See `Middleware()`.
func MiddlewareWithConfig(config Config) echo.MiddlewareFunc {
	// Defaults
	if config.Skipper == nil {
		config.Skipper = DefaultConfig.Skipper
	}
	if config.Store == nil {
		panic("echo: session middleware requires store")
	}

	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c *echo.Context) error {
			if config.Skipper(c) {
				return next(c)
			}
			c.Set(key, config.Store)
			return next(c)
		}
	}
}


================================================
FILE: session/session_test.go
================================================
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors

package session

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gorilla/sessions"
	"github.com/labstack/echo/v5"
	"github.com/stretchr/testify/assert"
)

func TestMiddleware(t *testing.T) {
	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)
	handler := func(c *echo.Context) error {
		sess, _ := Get("test", c)
		sess.Options.Domain = "labstack.com"
		sess.Values["foo"] = "bar"
		if err := sess.Save(c.Request(), c.Response()); err != nil {
			return err
		}
		return c.String(http.StatusOK, "test")
	}
	store := sessions.NewCookieStore([]byte("secret"))
	config := Config{
		Skipper: func(c *echo.Context) bool {
			return true
		},
		Store: store,
	}

	// Skipper
	mw := MiddlewareWithConfig(config)
	h := mw(func(c *echo.Context) error {
		return echo.ErrNotFound
	})
	assert.Error(t, h(c)) // 404
	assert.Nil(t, c.Get(key))

	// Panic
	config.Skipper = nil
	config.Store = nil
	assert.Panics(t, func() {
		MiddlewareWithConfig(config)
	})

	// Core
	mw = Middleware(store)
	h = mw(handler)
	assert.NoError(t, h(c))
	assert.Contains(t, rec.Header().Get(echo.HeaderSetCookie), "labstack.com")

}

func TestGetSessionMissingStore(t *testing.T) {
	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)
	_, err := Get("test", c)

	assert.EqualError(t, err, fmt.Sprintf("%q session store not found", key))
}


================================================
FILE: zipkintracing/README.md
================================================
# Tracing Library for Go

> 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
> `Go app -> OTLP -> OpenTelemetry Collector -> Zipkin`

This library provides tracing for go using [Zipkin](https://zipkin.io/)
 
## Usage

### Server Tracing Middleware & http client tracing

```go
package main

import (
	"io"
	"log/slog"
	"net/http"

	"github.com/labstack/echo-contrib/v5/zipkintracing"
	"github.com/labstack/echo/v5"
	"github.com/openzipkin/zipkin-go"
	zipkinhttp "github.com/openzipkin/zipkin-go/middleware/http"
	zipkinHttpReporter "github.com/openzipkin/zipkin-go/reporter/http"
)

func main() {
	e := echo.New()
	endpoint, err := zipkin.NewEndpoint("echo-service", "")
	if err != nil {
		slog.Error("failed to create zipkin endpoint", "error", err)
	}
	reporter := zipkinHttpReporter.NewReporter("http://localhost:9411/api/v2/spans")
	traceTags := make(map[string]string)
	traceTags["availability_zone"] = "us-east-1"
	tracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(endpoint), zipkin.WithTags(traceTags))
	client, _ := zipkinhttp.NewClient(tracer, zipkinhttp.ClientTrace(true))
	if err != nil {
		slog.Error("failed to create tracer", "error", err)
	}
	//Wrap & Use trace server middleware, this traces all server calls
	e.Use(zipkintracing.TraceServer(tracer))
	//....
	e.GET("/echo", func(c *echo.Context) error {
		//trace http request calls.
		req, _ := http.NewRequest("GET", "https://echo.labstack.com/", nil)
		resp, _ := zipkintracing.DoHTTP(c, req, client)
		body, _ := io.ReadAll(resp.Body)
		return c.String(http.StatusOK, string(body))
	})

	defer reporter.Close() //defer close reporter
	if err := e.Start(":8080"); err != nil {
		slog.Error("failed to start server", "error", err)
	}
}

```
### Reverse Proxy Tracing

```go
package main

import (
	"log/slog"
	"net/http/httputil"
	"net/url"

	"github.com/labstack/echo-contrib/v5/zipkintracing"
	"github.com/labstack/echo/v5"
	"github.com/openzipkin/zipkin-go"
	zipkinHttpReporter "github.com/openzipkin/zipkin-go/reporter/http"
)

func main() {
	e := echo.New()
	//new tracing instance
	endpoint, err := zipkin.NewEndpoint("echo-service", "")
	if err != nil {
		slog.Error("failed to create endpoint", "error", err)
	}
	reporter := zipkinHttpReporter.NewReporter("http://localhost:9411/api/v2/spans")
	traceTags := make(map[string]string)
	traceTags["availability_zone"] = "us-east-1"
	tracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(endpoint), zipkin.WithTags(traceTags))
	if err != nil {
		slog.Error("failed to create tracer", "error", err)
	}
	//....
	e.GET("/echo", func(c *echo.Context) error {
		proxyURL, _ := url.Parse("https://echo.labstack.com/")
		httputil.NewSingleHostReverseProxy(proxyURL)
		return nil
	}, zipkintracing.TraceProxy(tracer))

	defer reporter.Close() //close reporter
	if err := e.Start(":8080"); err != nil {
		slog.Error("failed to start server", "error", err)
	}
}
```

### Trace function calls

To trace function calls e.g. to trace `s3Func`

```go
package main

import (
	"github.com/labstack/echo-contrib/v5/zipkintracing"
	"github.com/labstack/echo/v5"
	"github.com/openzipkin/zipkin-go"
)

func s3Func(c *echo.Context, tracer *zipkin.Tracer) {
	defer zipkintracing.TraceFunc(c, "s3_read", zipkintracing.DefaultSpanTags, tracer)()
	//s3Func logic here...
}
```

### Create Child Span

```go
package main

import (
	"github.com/labstack/echo-contrib/v5/zipkintracing"
	"github.com/labstack/echo/v5"
	"github.com/openzipkin/zipkin-go"
)

func traceWithChildSpan(c *echo.Context, tracer *zipkin.Tracer) {
	span := zipkintracing.StartChildSpan(c, "someMethod", tracer)
	//func logic.....
	span.Finish()
}
```

================================================
FILE: zipkintracing/response_writer.go
================================================
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors

package zipkintracing

import (
	"bufio"
	"errors"
	"net"
	"net/http"
)

// ResponseWriter is a wrapper around http.ResponseWriter that provides extra information about
// the response. It is recommended that middleware handlers use this construct to wrap a response writer
// if the functionality calls for it.
type ResponseWriter interface {
	http.ResponseWriter
	http.Flusher
	// Status returns the status code of the response or 0 if the response has
	// not been written
	Status() int
	// Written returns whether or not the ResponseWriter has been written.
	Written() bool
	// Size returns the size of the response body.
	Size() int
	// Before allows for a function to be called before the ResponseWriter has been written to. This is
	// useful for setting headers or any other operations that must happen before a response has been written.
	Before(func(ResponseWriter))
}

type beforeFunc func(ResponseWriter)

// NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter
func NewResponseWriter(rw http.ResponseWriter) ResponseWriter {
	nrw := &responseWriter{
		ResponseWriter: rw,
	}

	return nrw
}

type responseWriter struct {
	http.ResponseWriter
	status      int
	size        int
	beforeFuncs []beforeFunc
}

func (rw *responseWriter) WriteHeader(s int) {
	rw.status = s
	rw.callBefore()
	rw.ResponseWriter.WriteHeader(s)
}

func (rw *responseWriter) Write(b []byte) (int, error) {
	if !rw.Written() {
		// The status will be StatusOK if WriteHeader has not been called yet
		rw.WriteHeader(http.StatusOK)
	}
	size, err := rw.ResponseWriter.Write(b)
	rw.size += size
	return size, err
}

func (rw *responseWriter) Status() int {
	return rw.status
}

func (rw *responseWriter) Size() int {
	return rw.size
}

func (rw *responseWriter) Written() bool {
	return rw.status != 0
}

func (rw *responseWriter) Before(before func(ResponseWriter)) {
	rw.beforeFuncs = append(rw.beforeFuncs, before)
}

func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	hijacker, ok := rw.ResponseWriter.(http.Hijacker)
	if !ok {
		return nil, nil, errors.New("the ResponseWriter doesn't support the Hijacker interface")
	}
	return hijacker.Hijack()
}

func (rw *responseWriter) callBefore() {
	for i := len(rw.beforeFuncs) - 1; i >= 0; i-- {
		rw.beforeFuncs[i](rw)
	}
}

func (rw *responseWriter) Flush() {
	flusher, ok := rw.ResponseWriter.(http.Flusher)
	if ok {
		if !rw.Written() {
			// The status will be StatusOK if WriteHeader has not been called yet
			rw.WriteHeader(http.StatusOK)
		}
		flusher.Flush()
	}
}

func (rw *responseWriter) CloseNotify() <-chan bool {
	//lint:ignore SA1019 we support it for backwards compatibility reasons
	return rw.ResponseWriter.(http.CloseNotifier).CloseNotify()
}

func (rw *responseWriter) Unwrap() http.ResponseWriter {
	return rw.ResponseWriter
}


================================================
FILE: zipkintracing/tracing.go
================================================
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors

package zipkintracing

import (
	"fmt"
	"net/http"
	"strconv"

	"github.com/labstack/echo/v5/middleware"

	"github.com/labstack/echo/v5"
	"github.com/openzipkin/zipkin-go"
	zipkinhttp "github.com/openzipkin/zipkin-go/middleware/http"
	"github.com/openzipkin/zipkin-go/model"
	"github.com/openzipkin/zipkin-go/propagation/b3"
)

type (

	//Tags func to adds span tags
	Tags func(c *echo.Context) map[string]string

	//TraceProxyConfig config for TraceProxyWithConfig
	TraceProxyConfig struct {
		Skipper  middleware.Skipper
		Tracer   *zipkin.Tracer
		SpanTags Tags
	}

	//TraceServerConfig config for TraceServerWithConfig
	TraceServerConfig struct {
		Skipper  middleware.Skipper
		Tracer   *zipkin.Tracer
		SpanTags Tags
	}
)

var (
	//DefaultSpanTags default span tags
	DefaultSpanTags = func(c *echo.Context) map[string]string {
		return make(map[string]string)
	}

	//DefaultTraceProxyConfig default config for Trace Proxy
	DefaultTraceProxyConfig = TraceProxyConfig{Skipper: middleware.DefaultSkipper, SpanTags: DefaultSpanTags}

	//DefaultTraceServerConfig default config for Trace Server
	DefaultTraceServerConfig = TraceServerConfig{Skipper: middleware.DefaultSkipper, SpanTags: DefaultSpanTags}
)

// DoHTTP is a http zipkin tracer implementation of HTTPDoer
//
// 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
func DoHTTP(c *echo.Context, r *http.Request, client *zipkinhttp.Client) (*http.Response, error) {
	req := r.WithContext(c.Request().Context())
	return client.DoWithAppSpan(req, req.Method)
}

// 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
//
// 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
func TraceFunc(c *echo.Context, spanName string, spanTags Tags, tracer *zipkin.Tracer) func() {
	span, _ := tracer.StartSpanFromContext(c.Request().Context(), spanName)
	for key, value := range spanTags(c) {
		span.Tag(key, value)
	}

	finishSpan := func() {
		span.Finish()
	}

	return finishSpan
}

// TraceProxy middleware that traces reverse proxy
//
// 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
func TraceProxy(tracer *zipkin.Tracer) echo.MiddlewareFunc {
	config := DefaultTraceProxyConfig
	config.Tracer = tracer
	return TraceProxyWithConfig(config)
}

// TraceProxyWithConfig middleware that traces reverse proxy
//
// 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
func TraceProxyWithConfig(config TraceProxyConfig) echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c *echo.Context) error {
			if config.Skipper(c) {
				return next(c)
			}
			var parentContext model.SpanContext
			if span := zipkin.SpanFromContext(c.Request().Context()); span != nil {
				parentContext = span.Context()
			}
			span := config.Tracer.StartSpan(fmt.Sprintf("C %s %s", c.Request().Method, "reverse proxy"), zipkin.Parent(parentContext))
			for key, value := range config.SpanTags(c) {
				span.Tag(key, value)
			}
			defer span.Finish()
			ctx := zipkin.NewContext(c.Request().Context(), span)
			c.SetRequest(c.Request().WithContext(ctx))
			b3.InjectHTTP(c.Request())(span.Context())
			nrw := NewResponseWriter(c.Response())
			if err := next(c); err != nil {
				c.Echo().HTTPErrorHandler(c, err)
			}
			if nrw.Size() > 0 {
				zipkin.TagHTTPResponseSize.Set(span, strconv.FormatInt(int64(nrw.Size()), 10))
			}
			if nrw.Status() < 200 || nrw.Status() > 299 {
				statusCode := strconv.FormatInt(int64(nrw.Status()), 10)
				zipkin.TagHTTPStatusCode.Set(span, statusCode)
				if nrw.Status() > 399 {
					zipkin.TagError.Set(span, statusCode)
				}
			}
			return nil
		}
	}
}

// TraceServer middleware that traces server calls
//
// 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
func TraceServer(tracer *zipkin.Tracer) echo.MiddlewareFunc {
	config := DefaultTraceServerConfig
	config.Tracer = tracer
	return TraceServerWithConfig(config)
}

// TraceServerWithConfig middleware that traces server calls
//
// 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
func TraceServerWithConfig(config TraceServerConfig) echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c *echo.Context) error {
			if config.Skipper(c) {
				return next(c)
			}
			sc := config.Tracer.Extract(b3.ExtractHTTP(c.Request()))
			span := config.Tracer.StartSpan(fmt.Sprintf("S %s %s", c.Request().Method, c.Request().URL.Path), zipkin.Parent(sc))
			for key, value := range config.SpanTags(c) {
				span.Tag(key, value)
			}
			defer span.Finish()
			ctx := zipkin.NewContext(c.Request().Context(), span)
			c.SetRequest(c.Request().WithContext(ctx))
			nrw := NewResponseWriter(c.Response())
			if err := next(c); err != nil {
				c.Echo().HTTPErrorHandler(c, err)
			}

			if nrw.Size() > 0 {
				zipkin.TagHTTPResponseSize.Set(span, strconv.FormatInt(int64(nrw.Size()), 10))
			}
			if nrw.Status() < 200 || nrw.Status() > 299 {
				statusCode := strconv.FormatInt(int64(nrw.Status()), 10)
				zipkin.TagHTTPStatusCode.Set(span, statusCode)
				if nrw.Status() > 399 {
					zipkin.TagError.Set(span, statusCode)
				}
			}
			return nil
		}
	}
}

// StartChildSpan starts a new child span as child of parent span from context
// user must call defer childSpan.Finish()
func StartChildSpan(c *echo.Context, spanName string, tracer *zipkin.Tracer) (childSpan zipkin.Span) {
	var parentContext model.SpanContext

	if span := zipkin.SpanFromContext(c.Request().Context()); span != nil {
		parentContext = span.Context()
	}
	childSpan = tracer.StartSpan(spanName, zipkin.Parent(parentContext))
	return childSpan
}


================================================
FILE: zipkintracing/tracing_test.go
================================================
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors

package zipkintracing

import (
	"encoding/json"
	"github.com/labstack/echo/v5"
	"github.com/labstack/echo/v5/middleware"
	"github.com/openzipkin/zipkin-go"
	zipkinhttp "github.com/openzipkin/zipkin-go/middleware/http"
	"github.com/openzipkin/zipkin-go/propagation/b3"
	"github.com/openzipkin/zipkin-go/reporter"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	zipkinHttpReporter "github.com/openzipkin/zipkin-go/reporter/http"
	"github.com/stretchr/testify/assert"
)

type zipkinSpanRequest struct {
	ID            string
	TraceID       string
	Timestamp     uint64
	Name          string
	LocalEndpoint struct {
		ServiceName string
	}
	Tags map[string]string
}

// DefaultTracer returns zipkin tracer with defaults for testing
func DefaultTracer(reportingURL, serviceName string, tags map[string]string) (*zipkin.Tracer, reporter.Reporter, error) {
	endpoint, err := zipkin.NewEndpoint(serviceName, "")
	if err != nil {
		return nil, nil, err
	}
	reporter := zipkinHttpReporter.NewReporter(reportingURL)
	tracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(endpoint), zipkin.WithTags(tags))
	if err != nil {
		return nil, nil, err
	}
	return tracer, reporter, nil
}

func TestDoHTTTP(t *testing.T) {
	done := make(chan struct{})
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer close(done)

		body, err := ioutil.ReadAll(r.Body)
		assert.NoError(t, err)

		var spans []zipkinSpanRequest
		err = json.Unmarshal(body, &spans)
		assert.NoError(t, err)

		assert.NotEmpty(t, spans[0].ID)
		assert.NotEmpty(t, spans[0].TraceID)
		assert.Equal(t, "http/get", spans[0].Name)
		assert.Equal(t, "echo-service", spans[0].LocalEndpoint.ServiceName)
	}))
	defer ts.Close()

	echoServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		assert.Equal(t, r.Method, http.MethodGet)
		assert.NotEmpty(t, r.Header.Get(b3.TraceID))
		assert.NotEmpty(t, r.Header.Get(b3.SpanID))
	}))
	defer echoServer.Close()
	tracer, reporter, err := DefaultTracer(ts.URL, "echo-service", nil)
	req := httptest.NewRequest(http.MethodGet, echoServer.URL, nil)
	req.RequestURI = ""
	rec := httptest.NewRecorder()
	assert.NoError(t, err)
	e := echo.New()
	c := e.NewContext(req, rec)
	client, err := zipkinhttp.NewClient(tracer)
	assert.NoError(t, err)
	_, err = DoHTTP(c, req, client)
	assert.NoError(t, err)
	err = reporter.Close()
	assert.NoError(t, err)

	select {
	case <-done:
	case <-time.After(time.Millisecond * 1500):
		t.Fatalf("Test server did not receive spans")
	}
}

func TestTraceFunc(t *testing.T) {
	done := make(chan struct{})
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer close(done)

		body, err := ioutil.ReadAll(r.Body)
		assert.NoError(t, err)

		var spans []zipkinSpanRequest
		err = json.Unmarshal(body, &spans)
		assert.NoError(t, err)
		assert.NotEmpty(t, spans[0].ID)
		assert.NotEmpty(t, spans[0].TraceID)
		assert.Equal(t, "s3_read", spans[0].Name)
		assert.Equal(t, "echo-service", spans[0].LocalEndpoint.ServiceName)
		assert.NotNil(t, spans[0].Tags["availability_zone"])
		assert.Equal(t, "us-east-1", spans[0].Tags["availability_zone"])
	}))
	defer ts.Close()
	e := echo.New()
	req := httptest.NewRequest("GET", "http://localhost:8080/echo", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)
	traceTags := make(map[string]string)
	traceTags["availability_zone"] = "us-east-1"
	tracer, reporter, err := DefaultTracer(ts.URL, "echo-service", traceTags)
	assert.NoError(t, err)
	s3func := func(name string) {
		TraceFunc(c, "s3_read", DefaultSpanTags, tracer)()
		assert.Equal(t, "s3Test", name)
	}
	s3func("s3Test")
	err = reporter.Close()
	assert.NoError(t, err)
	select {
	case <-done:
	case <-time.After(time.Millisecond * 15500):
		t.Fatalf("Test server did not receive spans")
	}
}

func TestTraceProxy(t *testing.T) {
	done := make(chan struct{})
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer close(done)

		body, err := ioutil.ReadAll(r.Body)
		assert.NoError(t, err)

		var spans []zipkinSpanRequest
		err = json.Unmarshal(body, &spans)
		assert.NoError(t, err)

		assert.NotEmpty(t, spans[0].ID)
		assert.NotEmpty(t, spans[0].TraceID)
		assert.Equal(t, "c get reverse proxy", spans[0].Name)
		assert.Equal(t, "echo-service", spans[0].LocalEndpoint.ServiceName)
		assert.NotNil(t, spans[0].Tags["availability_zone"])
		assert.Equal(t, "us-east-1", spans[0].Tags["availability_zone"])
	}))
	defer ts.Close()
	traceTags := make(map[string]string)
	traceTags["availability_zone"] = "us-east-1"
	tracer, reporter, err := DefaultTracer(ts.URL, "echo-service", traceTags)
	req := httptest.NewRequest("GET", "http://localhost:8080/accounts/acctrefid/transactions", nil)
	rec := httptest.NewRecorder()
	e := echo.New()
	c := e.NewContext(req, rec)
	mw := TraceProxy(tracer)
	h := mw(func(c *echo.Context) error {
		return nil
	})
	err = h(c)
	assert.NoError(t, err)
	assert.NotEmpty(t, req.Header.Get(b3.TraceID))
	assert.NotEmpty(t, req.Header.Get(b3.SpanID))

	err = reporter.Close()
	assert.NoError(t, err)

	select {
	case <-done:
	case <-time.After(time.Millisecond * 1500):
		t.Fatalf("Test server did not receive spans")
	}
}

func TestTraceServer(t *testing.T) {
	done := make(chan struct{})
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer close(done)

		body, err := ioutil.ReadAll(r.Body)
		assert.NoError(t, err)

		var spans []zipkinSpanRequest
		err = json.Unmarshal(body, &spans)
		assert.NoError(t, err)

		assert.NotEmpty(t, spans[0].ID)
		assert.NotEmpty(t, spans[0].TraceID)
		assert.Equal(t, "s get /accounts/acctrefid/transactions", spans[0].Name)
		assert.Equal(t, "echo-service", spans[0].LocalEndpoint.ServiceName)
		assert.NotNil(t, spans[0].Tags["availability_zone"])
		assert.Equal(t, "us-east-1", spans[0].Tags["availability_zone"])
	}))
	defer ts.Close()
	traceTags := make(map[string]string)
	traceTags["availability_zone"] = "us-east-1"
	tracer, reporter, err := DefaultTracer(ts.URL, "echo-service", traceTags)
	req := httptest.NewRequest("GET", "http://localhost:8080/accounts/acctrefid/transactions", nil)
	rec := httptest.NewRecorder()
	mw := TraceServer(tracer)
	h := mw(func(c *echo.Context) error {
		return nil
	})
	assert.NoError(t, err)
	e := echo.New()
	c := e.NewContext(req, rec)
	err = h(c)
	err = reporter.Close()
	assert.NoError(t, err)

	select {
	case <-done:
	case <-time.After(time.Millisecond * 1500):
		t.Fatalf("Test server did not receive spans")
	}
}

func TestTraceServerWithConfig(t *testing.T) {
	done := make(chan struct{})
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer close(done)

		body, err := ioutil.ReadAll(r.Body)
		assert.NoError(t, err)

		var spans []zipkinSpanRequest
		err = json.Unmarshal(body, &spans)
		assert.NoError(t, err)

		assert.NotEmpty(t, spans[0].ID)
		assert.NotEmpty(t, spans[0].TraceID)
		assert.Equal(t, "s get /accounts/acctrefid/transactions", spans[0].Name)
		assert.Equal(t, "echo-service", spans[0].LocalEndpoint.ServiceName)
		assert.NotNil(t, spans[0].Tags["availability_zone"])
		assert.Equal(t, "us-east-1", spans[0].Tags["availability_zone"])
		assert.NotNil(t, spans[0].Tags["Client-Correlation-Id"])
		assert.Equal(t, "c98404736319", spans[0].Tags["Client-Correlation-Id"])

	}))
	defer ts.Close()
	traceTags := make(map[string]string)
	traceTags["availability_zone"] = "us-east-1"
	tracer, reporter, err := DefaultTracer(ts.URL, "echo-service", traceTags)
	req := httptest.NewRequest("GET", "http://localhost:8080/accounts/acctrefid/transactions", nil)
	req.Header.Add("Client-Correlation-Id", "c98404736319")
	rec := httptest.NewRecorder()
	tags := func(c *echo.Context) map[string]string {
		tags := make(map[string]string)
		correlationID := c.Request().Header.Get("Client-Correlation-Id")
		tags["Client-Correlation-Id"] = correlationID
		return tags
	}
	config := TraceServerConfig{Skipper: middleware.DefaultSkipper, SpanTags: tags, Tracer: tracer}
	mw := TraceServerWithConfig(config)
	h := mw(func(c *echo.Context) error {
		return nil
	})
	assert.NoError(t, err)
	e := echo.New()
	c := e.NewContext(req, rec)
	err = h(c)
	err = reporter.Close()
	assert.NoError(t, err)
	select {
	case <-done:
	case <-time.After(time.Millisecond * 1500):
		t.Fatalf("Test server did not receive spans")
	}
}

func TestTraceServerWithConfigSkipper(t *testing.T) {
	done := make(chan struct{})
	neverCalled := false
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer close(done)
		body, err := ioutil.ReadAll(r.Body)
		assert.NoError(t, err)
		var spans []zipkinSpanRequest
		err = json.Unmarshal(body, &spans)
		assert.NoError(t, err)
	}))
	defer ts.Close()
	traceTags := make(map[string]string)
	tracer, reporter, err := DefaultTracer(ts.URL, "echo-service", traceTags)
	traceTags["availability_zone"] = "us-east-1"
	req := httptest.NewRequest("GET", "http://localhost:8080/health", nil)
	rec := httptest.NewRecorder()
	config := TraceServerConfig{Skipper: func(c *echo.Context) bool {
		return c.Request().URL.Path == "/health"
	}, Tracer: tracer}
	mw := TraceServerWithConfig(config)
	h := mw(func(c *echo.Context) error {
		return nil
	})
	assert.NoError(t, err)
	e := echo.New()
	c := e.NewContext(req, rec)
	err = h(c)
	err = reporter.Close()
	assert.NoError(t, err)
	select {
	case <-done:
	case <-time.After(time.Millisecond * 500):
		neverCalled = true
	}
	assert.True(t, neverCalled)
}

func TestStartChildSpan(t *testing.T) {
	done := make(chan struct{})
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer close(done)

		body, err := ioutil.ReadAll(r.Body)
		assert.NoError(t, err)

		var spans []zipkinSpanRequest
		err = json.Unmarshal(body, &spans)
		assert.NoError(t, err)
		assert.NotEmpty(t, spans[0].ID)
		assert.NotEmpty(t, spans[0].TraceID)
		assert.Equal(t, "kinesis-test", spans[0].Name)
		assert.Equal(t, "echo-service", spans[0].LocalEndpoint.ServiceName)
		assert.NotNil(t, spans[0].Tags["availability_zone"])
		assert.Equal(t, "us-east-1", spans[0].Tags["availability_zone"])
	}))
	defer ts.Close()
	traceTags := make(map[string]string)
	traceTags["availability_zone"] = "us-east-1"
	tracer, reporter, err := DefaultTracer(ts.URL, "echo-service", traceTags)
	assert.NoError(t, err)

	req := httptest.NewRequest("GET", "http://localhost:8080/health", nil)
	rec := httptest.NewRecorder()
	e := echo.New()
	c := e.NewContext(req, rec)

	childSpan := StartChildSpan(c, "kinesis-test", tracer)
	time.Sleep(500)
	childSpan.Finish()
	assert.NoError(t, err)
	err = reporter.Close()
	assert.NoError(t, err)
	select {
	case <-done:
	case <-time.After(time.Millisecond * 15500):
		t.Fatalf("Test server did not receive spans")
	}
}
Download .txt
gitextract_lu6se63q/

├── .github/
│   ├── ISSUE_TEMPLATE.md
│   ├── stale.yml
│   └── workflows/
│       └── echo-contrib.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── casbin/
│   ├── README.md
│   ├── auth_model.conf
│   ├── auth_policy.csv
│   ├── broken_auth_model.conf
│   ├── casbin.go
│   └── casbin_test.go
├── codecov.yml
├── echo.go
├── echoprometheus/
│   ├── README.md
│   ├── prometheus.go
│   └── prometheus_test.go
├── go.mod
├── go.sum
├── internal/
│   └── helpers/
│       └── statuscode.go
├── jaegertracing/
│   ├── jaegertracing.go
│   ├── jaegertracing_test.go
│   └── response_dumper.go
├── pprof/
│   ├── README.md
│   ├── pprof.go
│   └── pprof_test.go
├── session/
│   ├── session.go
│   └── session_test.go
└── zipkintracing/
    ├── README.md
    ├── response_writer.go
    ├── tracing.go
    └── tracing_test.go
Download .txt
SYMBOL INDEX (149 symbols across 15 files)

FILE: casbin/casbin.go
  type Config (line 61) | type Config struct
  function Middleware (line 99) | func Middleware(ce *casbin.Enforcer) echo.MiddlewareFunc {
  function MiddlewareWithConfig (line 107) | func MiddlewareWithConfig(config Config) echo.MiddlewareFunc {

FILE: casbin/casbin_test.go
  function testRequest (line 20) | func testRequest(t *testing.T, h echo.HandlerFunc, user string, path str...
  function TestAuth (line 47) | func TestAuth(t *testing.T) {
  function TestPathWildcard (line 59) | func TestPathWildcard(t *testing.T) {
  function TestRBAC (line 80) | func TestRBAC(t *testing.T) {
  function TestEnforceError (line 105) | func TestEnforceError(t *testing.T) {
  function TestCustomUserGetter (line 114) | func TestCustomUserGetter(t *testing.T) {
  function TestUserGetterError (line 129) | func TestUserGetterError(t *testing.T) {
  function TestCustomEnforceHandler (line 144) | func TestCustomEnforceHandler(t *testing.T) {
  function TestCustomSkipper (line 168) | func TestCustomSkipper(t *testing.T) {

FILE: echoprometheus/prometheus.go
  constant defaultSubsystem (line 32) | defaultSubsystem = "echo"
  constant _ (line 36) | _           = iota
  constant bKB (line 37) | bKB float64 = 1 << (10 * iota)
  constant bMB (line 38) | bMB
  type MiddlewareConfig (line 45) | type MiddlewareConfig struct
    method ToMiddleware (line 164) | func (conf MiddlewareConfig) ToMiddleware() (echo.MiddlewareFunc, erro...
  type LabelValueFunc (line 89) | type LabelValueFunc
  type HandlerConfig (line 92) | type HandlerConfig struct
  type PushGatewayConfig (line 99) | type PushGatewayConfig struct
  function NewHandler (line 122) | func NewHandler() echo.HandlerFunc {
  function NewHandlerWithConfig (line 127) | func NewHandlerWithConfig(config HandlerConfig) echo.HandlerFunc {
  function NewMiddleware (line 146) | func NewMiddleware(subsystem string) echo.MiddlewareFunc {
  function NewMiddlewareWithConfig (line 153) | func NewMiddlewareWithConfig(config MiddlewareConfig) echo.MiddlewareFunc {
  type customLabelValuer (line 315) | type customLabelValuer struct
  function createLabels (line 321) | func createLabels(customLabelFuncs map[string]LabelValueFunc) ([]string,...
  function containsAt (line 350) | func containsAt[K comparable](haystack []K, needle K) int {
  function computeApproximateRequestSize (line 359) | func computeApproximateRequestSize(r *http.Request) int {
  function RunPushGatewayGatherer (line 401) | func RunPushGatewayGatherer(ctx context.Context, config PushGatewayConfi...
  function WriteGatheredMetrics (line 460) | func WriteGatheredMetrics(writer io.Writer, gatherer prometheus.Gatherer...

FILE: echoprometheus/prometheus_test.go
  function TestCustomRegistryMetrics (line 22) | func TestCustomRegistryMetrics(t *testing.T) {
  function TestDefaultRegistryMetrics (line 36) | func TestDefaultRegistryMetrics(t *testing.T) {
  function TestPrometheus_Buckets (line 51) | func TestPrometheus_Buckets(t *testing.T) {
  function TestMiddlewareConfig_Skipper (line 70) | func TestMiddlewareConfig_Skipper(t *testing.T) {
  function TestMetricsForErrors (line 103) | func TestMetricsForErrors(t *testing.T) {
  function TestMiddlewareConfig_LabelFuncs (line 138) | func TestMiddlewareConfig_LabelFuncs(t *testing.T) {
  function TestMiddlewareConfig_StatusCodeResolver (line 165) | func TestMiddlewareConfig_StatusCodeResolver(t *testing.T) {
  function TestMiddlewareConfig_HistogramOptsFunc (line 226) | func TestMiddlewareConfig_HistogramOptsFunc(t *testing.T) {
  function TestMiddlewareConfig_CounterOptsFunc (line 255) | func TestMiddlewareConfig_CounterOptsFunc(t *testing.T) {
  function TestMiddlewareConfig_AfterNextFuncs (line 284) | func TestMiddlewareConfig_AfterNextFuncs(t *testing.T) {
  function TestRunPushGatewayGatherer (line 317) | func TestRunPushGatewayGatherer(t *testing.T) {
  function TestSetPathFor404NoMatchingRoute (line 345) | func TestSetPathFor404NoMatchingRoute(t *testing.T) {
  function TestSetPathFor404Logic (line 362) | func TestSetPathFor404Logic(t *testing.T) {
  function TestInvalidUTF8PathIsFixed (line 383) | func TestInvalidUTF8PathIsFixed(t *testing.T) {
  function requestBody (line 398) | func requestBody(e *echo.Echo, path string) (string, int) {
  function request (line 406) | func request(e *echo.Echo, path string) int {
  function unregisterDefaults (line 411) | func unregisterDefaults(subsystem string) {

FILE: internal/helpers/statuscode.go
  function DefaultStatusResolver (line 11) | func DefaultStatusResolver(c *echo.Context, err error) int {

FILE: jaegertracing/jaegertracing.go
  constant defaultComponentName (line 51) | defaultComponentName = "echo/v5"
  type TraceConfig (line 55) | type TraceConfig struct
  function New (line 97) | func New(e *echo.Echo, skipper middleware.Skipper) io.Closer {
  function Trace (line 131) | func Trace(tracer opentracing.Tracer) echo.MiddlewareFunc {
  function TraceWithConfig (line 142) | func TraceWithConfig(config TraceConfig) echo.MiddlewareFunc {
  function limitString (line 252) | func limitString(str string, size int) string {
  function logError (line 260) | func logError(span opentracing.Span, err error) {
  function getRequestID (line 270) | func getRequestID(ctx *echo.Context) string {
  function generateToken (line 278) | func generateToken() string {
  function defaultOperationName (line 284) | func defaultOperationName(c *echo.Context) string {
  function TraceFunction (line 292) | func TraceFunction(ctx *echo.Context, fn interface{}, params ...interfac...
  function CreateChildSpan (line 329) | func CreateChildSpan(ctx *echo.Context, name string) opentracing.Span {
  function NewTracedRequest (line 350) | func NewTracedRequest(method string, url string, body io.Reader, span op...

FILE: jaegertracing/jaegertracing_test.go
  type mockSpan (line 22) | type mockSpan struct
    method isFinished (line 38) | func (sp *mockSpan) isFinished() bool {
    method getOpName (line 42) | func (sp *mockSpan) getOpName() string {
    method getTag (line 46) | func (sp *mockSpan) getTag(key string) interface{} {
    method getLog (line 50) | func (sp *mockSpan) getLog(key string) interface{} {
    method Finish (line 54) | func (sp *mockSpan) Finish() {
    method FinishWithOptions (line 57) | func (sp *mockSpan) FinishWithOptions(opts opentracing.FinishOptions) {
    method Context (line 59) | func (sp *mockSpan) Context() opentracing.SpanContext {
    method SetOperationName (line 62) | func (sp *mockSpan) SetOperationName(operationName string) opentracing...
    method SetTag (line 66) | func (sp *mockSpan) SetTag(key string, value interface{}) opentracing....
    method LogFields (line 70) | func (sp *mockSpan) LogFields(fields ...log.Field) {
    method LogKV (line 72) | func (sp *mockSpan) LogKV(alternatingKeyValues ...interface{}) {
    method SetBaggageItem (line 81) | func (sp *mockSpan) SetBaggageItem(restrictedKey, value string) opentr...
    method BaggageItem (line 84) | func (sp *mockSpan) BaggageItem(restrictedKey string) string {
    method Tracer (line 87) | func (sp *mockSpan) Tracer() opentracing.Tracer {
    method LogEvent (line 90) | func (sp *mockSpan) LogEvent(event string) {
    method LogEventWithPayload (line 92) | func (sp *mockSpan) LogEventWithPayload(event string, payload interfac...
    method Log (line 94) | func (sp *mockSpan) Log(data opentracing.LogData) {
  function createSpan (line 30) | func createSpan(tracer opentracing.Tracer) *mockSpan {
  type mockTracer (line 98) | type mockTracer struct
    method currentSpan (line 103) | func (tr *mockTracer) currentSpan() *mockSpan {
    method StartSpan (line 107) | func (tr *mockTracer) StartSpan(operationName string, opts ...opentrac...
    method Inject (line 118) | func (tr *mockTracer) Inject(sm opentracing.SpanContext, format interf...
    method Extract (line 122) | func (tr *mockTracer) Extract(format interface{}, carrier interface{})...
  function createMockTracer (line 129) | func createMockTracer() *mockTracer {
  function TestTraceWithDefaultConfig (line 136) | func TestTraceWithDefaultConfig(t *testing.T) {
  function TestTraceWithConfig (line 199) | func TestTraceWithConfig(t *testing.T) {
  function TestTraceWithConfigOfBodyDump (line 218) | func TestTraceWithConfigOfBodyDump(t *testing.T) {
  function TestTraceWithConfigOfNoneComponentName (line 246) | func TestTraceWithConfigOfNoneComponentName(t *testing.T) {
  function TestTraceWithConfigOfSkip (line 261) | func TestTraceWithConfigOfSkip(t *testing.T) {
  function TestTraceOfNoCurrentSpan (line 277) | func TestTraceOfNoCurrentSpan(t *testing.T) {
  function TestTraceWithLimitHTTPBody (line 288) | func TestTraceWithLimitHTTPBody(t *testing.T) {
  function TestTraceWithoutLimitHTTPBody (line 312) | func TestTraceWithoutLimitHTTPBody(t *testing.T) {
  function TestTraceWithDefaultOperationName (line 336) | func TestTraceWithDefaultOperationName(t *testing.T) {
  function TestTraceWithCustomOperationName (line 353) | func TestTraceWithCustomOperationName(t *testing.T) {

FILE: jaegertracing/response_dumper.go
  type responseDumper (line 12) | type responseDumper struct
    method Write (line 29) | func (d *responseDumper) Write(b []byte) (int, error) {
    method GetResponse (line 33) | func (d *responseDumper) GetResponse() string {
    method Unwrap (line 37) | func (d *responseDumper) Unwrap() http.ResponseWriter {
  function newResponseDumper (line 19) | func newResponseDumper(resp http.ResponseWriter) *responseDumper {

FILE: pprof/pprof.go
  constant DefaultPrefix (line 15) | DefaultPrefix = "/debug/pprof"
  function getPrefix (line 18) | func getPrefix(prefixOptions ...string) string {
  function Register (line 26) | func Register(e *echo.Echo, prefixOptions ...string) {
  function handler (line 46) | func handler(h http.HandlerFunc) echo.HandlerFunc {

FILE: pprof/pprof_test.go
  function TestPProfRegisterDefaualtPrefix (line 15) | func TestPProfRegisterDefaualtPrefix(t *testing.T) {
  function TestPProfRegisterCustomPrefix (line 44) | func TestPProfRegisterCustomPrefix(t *testing.T) {

FILE: session/session.go
  type Config (line 16) | type Config struct
  constant key (line 27) | key = "_session_store"
  function Get (line 38) | func Get(name string, c *echo.Context) (*sessions.Session, error) {
  function Middleware (line 48) | func Middleware(store sessions.Store) echo.MiddlewareFunc {
  function MiddlewareWithConfig (line 56) | func MiddlewareWithConfig(config Config) echo.MiddlewareFunc {

FILE: session/session_test.go
  function TestMiddleware (line 17) | func TestMiddleware(t *testing.T) {
  function TestGetSessionMissingStore (line 62) | func TestGetSessionMissingStore(t *testing.T) {

FILE: zipkintracing/response_writer.go
  type ResponseWriter (line 16) | type ResponseWriter interface
  type beforeFunc (line 31) | type beforeFunc
  function NewResponseWriter (line 34) | func NewResponseWriter(rw http.ResponseWriter) ResponseWriter {
  type responseWriter (line 42) | type responseWriter struct
    method WriteHeader (line 49) | func (rw *responseWriter) WriteHeader(s int) {
    method Write (line 55) | func (rw *responseWriter) Write(b []byte) (int, error) {
    method Status (line 65) | func (rw *responseWriter) Status() int {
    method Size (line 69) | func (rw *responseWriter) Size() int {
    method Written (line 73) | func (rw *responseWriter) Written() bool {
    method Before (line 77) | func (rw *responseWriter) Before(before func(ResponseWriter)) {
    method Hijack (line 81) | func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
    method callBefore (line 89) | func (rw *responseWriter) callBefore() {
    method Flush (line 95) | func (rw *responseWriter) Flush() {
    method CloseNotify (line 106) | func (rw *responseWriter) CloseNotify() <-chan bool {
    method Unwrap (line 111) | func (rw *responseWriter) Unwrap() http.ResponseWriter {

FILE: zipkintracing/tracing.go
  type Tags (line 23) | type Tags
  type TraceProxyConfig (line 26) | type TraceProxyConfig struct
  type TraceServerConfig (line 33) | type TraceServerConfig struct
  function DoHTTP (line 56) | func DoHTTP(c *echo.Context, r *http.Request, client *zipkinhttp.Client)...
  function TraceFunc (line 64) | func TraceFunc(c *echo.Context, spanName string, spanTags Tags, tracer *...
  function TraceProxy (line 80) | func TraceProxy(tracer *zipkin.Tracer) echo.MiddlewareFunc {
  function TraceProxyWithConfig (line 89) | func TraceProxyWithConfig(config TraceProxyConfig) echo.MiddlewareFunc {
  function TraceServer (line 129) | func TraceServer(tracer *zipkin.Tracer) echo.MiddlewareFunc {
  function TraceServerWithConfig (line 138) | func TraceServerWithConfig(config TraceServerConfig) echo.MiddlewareFunc {
  function StartChildSpan (line 174) | func StartChildSpan(c *echo.Context, spanName string, tracer *zipkin.Tra...

FILE: zipkintracing/tracing_test.go
  type zipkinSpanRequest (line 24) | type zipkinSpanRequest struct
  function DefaultTracer (line 36) | func DefaultTracer(reportingURL, serviceName string, tags map[string]str...
  function TestDoHTTTP (line 49) | func TestDoHTTTP(t *testing.T) {
  function TestTraceFunc (line 95) | func TestTraceFunc(t *testing.T) {
  function TestTraceProxy (line 136) | func TestTraceProxy(t *testing.T) {
  function TestTraceServer (line 182) | func TestTraceServer(t *testing.T) {
  function TestTraceServerWithConfig (line 225) | func TestTraceServerWithConfig(t *testing.T) {
  function TestTraceServerWithConfigSkipper (line 278) | func TestTraceServerWithConfigSkipper(t *testing.T) {
  function TestStartChildSpan (line 316) | func TestStartChildSpan(t *testing.T) {
Condensed preview — 33 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (130K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "chars": 280,
    "preview": "### Issue Description\n\n### Checklist\n\n- [ ] Dependencies installed\n- [ ] No typos\n- [ ] Searched existing issues and doc"
  },
  {
    "path": ".github/stale.yml",
    "chars": 722,
    "preview": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 60\n# Number of days of inactivity before a "
  },
  {
    "path": ".github/workflows/echo-contrib.yml",
    "chars": 2450,
    "preview": "name: Run Tests\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\n  workflow_dispat"
  },
  {
    "path": ".gitignore",
    "chars": 31,
    "preview": "vendor\n_test\ncoverage.txt\n.idea"
  },
  {
    "path": "LICENSE",
    "chars": 1075,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2017 LabStack\n\nPermission is hereby granted, free of charge, to any person obtainin"
  },
  {
    "path": "Makefile",
    "chars": 1030,
    "preview": "PKG := \"github.com/labstack/echo-contrib\"\nPKG_LIST := $(shell go list ${PKG}/...)\n\n.DEFAULT_GOAL := check\ncheck: lint ve"
  },
  {
    "path": "README.md",
    "chars": 2393,
    "preview": "# Echo Community Contribution middlewares\n\n [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-s"
  },
  {
    "path": "casbin/README.md",
    "chars": 1096,
    "preview": "# 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/"
  },
  {
    "path": "casbin/auth_model.conf",
    "chars": 250,
    "preview": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[policy_effec"
  },
  {
    "path": "casbin/auth_policy.csv",
    "chars": 220,
    "preview": "p, alice, /dataset1/*, GET\np, alice, /dataset1/resource1, POST\np, bob, /dataset2/resource1, *\np, bob, /dataset2/resource"
  },
  {
    "path": "casbin/broken_auth_model.conf",
    "chars": 245,
    "preview": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[policy_effec"
  },
  {
    "path": "casbin/casbin.go",
    "chars": 3838,
    "preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\n/* Package casbin prov"
  },
  {
    "path": "casbin/casbin_test.go",
    "chars": 6866,
    "preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage casbin\n\nimport"
  },
  {
    "path": "codecov.yml",
    "chars": 151,
    "preview": "coverage:\n  status:\n    project:\n      default:\n        threshold: 1%\n    patch:\n      default:\n        threshold: 1%\n\nc"
  },
  {
    "path": "echo.go",
    "chars": 111,
    "preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage echo\n"
  },
  {
    "path": "echoprometheus/README.md",
    "chars": 610,
    "preview": "# Usage\n\nDeprecated: use new repository [echo-prometheus middleware](https://github.com/labstack/echo-prometheus) or [Op"
  },
  {
    "path": "echoprometheus/prometheus.go",
    "chars": 15903,
    "preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\n/*\nPackage echoprometh"
  },
  {
    "path": "echoprometheus/prometheus_test.go",
    "chars": 16924,
    "preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage echoprometheus"
  },
  {
    "path": "go.mod",
    "chars": 1469,
    "preview": "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/goril"
  },
  {
    "path": "go.sum",
    "chars": 8673,
    "preview": "github.com/HdrHistogram/hdrhistogram-go v1.2.0 h1:XMJkDWuz6bM9Fzy7zORuVFKH7ZJY41G2q8KWhVGkNiY=\ngithub.com/HdrHistogram/h"
  },
  {
    "path": "internal/helpers/statuscode.go",
    "chars": 536,
    "preview": "package helpers\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\n\t\"github.com/labstack/echo/v5\"\n)\n\n// DefaultStatusResolver resolves htt"
  },
  {
    "path": "jaegertracing/jaegertracing.go",
    "chars": 11152,
    "preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\n/*\nPackage jaegertraci"
  },
  {
    "path": "jaegertracing/jaegertracing_test.go",
    "chars": 11102,
    "preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage jaegertracing\n"
  },
  {
    "path": "jaegertracing/response_dumper.go",
    "chars": 681,
    "preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage jaegertracing\n"
  },
  {
    "path": "pprof/README.md",
    "chars": 883,
    "preview": "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"
  },
  {
    "path": "pprof/pprof.go",
    "chars": 1496,
    "preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage pprof\n\nimport "
  },
  {
    "path": "pprof/pprof_test.go",
    "chars": 1448,
    "preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage pprof\n\nimport "
  },
  {
    "path": "session/session.go",
    "chars": 1591,
    "preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage session\n\nimpor"
  },
  {
    "path": "session/session_test.go",
    "chars": 1588,
    "preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage session\n\nimpor"
  },
  {
    "path": "zipkintracing/README.md",
    "chars": 3880,
    "preview": "# Tracing Library for Go\n\n> Deprecated: use [OpenTelemetry middleware](https://github.com/labstack/echo-opentelemetry) i"
  },
  {
    "path": "zipkintracing/response_writer.go",
    "chars": 2925,
    "preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage zipkintracing\n"
  },
  {
    "path": "zipkintracing/tracing.go",
    "chars": 7090,
    "preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage zipkintracing\n"
  },
  {
    "path": "zipkintracing/tracing_test.go",
    "chars": 10970,
    "preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2017 LabStack and Echo contributors\n\npackage zipkintracing\n"
  }
]

About this extraction

This page contains the full source code of the labstack/echo-contrib GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 33 files (116.9 KB), approximately 35.1k tokens, and a symbol index with 149 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!