very rudimentary impl
This commit is contained in:
36
.gitignore
vendored
36
.gitignore
vendored
@@ -1,22 +1,22 @@
|
|||||||
# ---> Rust
|
# Binaries for programs and plugins
|
||||||
# Generated by Cargo
|
*.exe
|
||||||
# will have compiled files and executables
|
*.exe~
|
||||||
debug/
|
*.dll
|
||||||
target/
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
# Test binary, built with `go test -c`
|
||||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
*.test
|
||||||
Cargo.lock
|
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
**/*.rs.bk
|
*.out
|
||||||
|
|
||||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
# Dependency directories (remove the comment below to include it)
|
||||||
*.pdb
|
# vendor/
|
||||||
|
|
||||||
# RustRover
|
# Go workspace file
|
||||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
go.work
|
||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
go.work.sum
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# env file
|
||||||
#.idea/
|
.env
|
||||||
8
go.mod
Normal file
8
go.mod
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
module ade9/llmproxymetrics
|
||||||
|
|
||||||
|
go 1.23.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/caarlos0/env v3.5.0+incompatible
|
||||||
|
github.com/caarlos0/env/v11 v11.3.1 // indirect
|
||||||
|
)
|
||||||
4
go.sum
Normal file
4
go.sum
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs=
|
||||||
|
github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y=
|
||||||
|
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
|
||||||
|
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
|
||||||
105
llmproxymetrics.go
Normal file
105
llmproxymetrics.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/caarlos0/env/v11"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cfg config
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
BaseURL string `env:"BASE_URL"`
|
||||||
|
Port int `env:"PORT"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func createProxy(target *url.URL) func(http.ResponseWriter, *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
r.Host = target.Host
|
||||||
|
r.URL.Scheme = target.Scheme
|
||||||
|
r.URL.Host = target.Host
|
||||||
|
|
||||||
|
lrw := &LoggingResponseWriter{ResponseWriter: w, body: new(bytes.Buffer)}
|
||||||
|
proxy := httputil.NewSingleHostReverseProxy(target)
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
proxy.ServeHTTP(recorder, r)
|
||||||
|
|
||||||
|
responseBody := recorder.Body.Bytes()
|
||||||
|
|
||||||
|
var jsonResponse map[string]interface{}
|
||||||
|
err := json.Unmarshal(responseBody, &jsonResponse)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error unmarshalling JSON response: %v", err)
|
||||||
|
lrw.Write(responseBody)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add your metrics metadata here
|
||||||
|
jsonResponse["metrics"] = map[string]interface{}{
|
||||||
|
"requestPath": r.URL.Path,
|
||||||
|
"statusCode": recorder.Code,
|
||||||
|
"responseTime": time.Since(startTime).Milliseconds(),
|
||||||
|
// Add more metrics as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
modifiedResponseBody, err := json.Marshal(jsonResponse)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error marshalling modified JSON response: %v", err)
|
||||||
|
lrw.Write(responseBody)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, values := range recorder.Header() {
|
||||||
|
for _, value := range values {
|
||||||
|
w.Header().Add(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(recorder.Code)
|
||||||
|
lrw.Write(modifiedResponseBody)
|
||||||
|
|
||||||
|
log.Printf("Response with metrics: %s", lrw.Body())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoggingResponseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
body *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lrw *LoggingResponseWriter) Write(b []byte) (int, error) {
|
||||||
|
lrw.body.Write(b)
|
||||||
|
return lrw.ResponseWriter.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lrw *LoggingResponseWriter) Body() string {
|
||||||
|
return lrw.body.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
env.Parse(&cfg)
|
||||||
|
|
||||||
|
targetURL, err := url.Parse(cfg.BaseURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
http.HandleFunc("/", createProxy(targetURL))
|
||||||
|
|
||||||
|
log.Printf("Starting proxy server on :%s", strconv.Itoa(cfg.Port))
|
||||||
|
err = http.ListenAndServe(fmt.Sprintf(":%s", strconv.Itoa(cfg.Port)), nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user