very rudimentary impl

This commit is contained in:
2025-04-20 13:47:17 +03:00
parent 09fdd4ec70
commit e71ff0b785
4 changed files with 135 additions and 18 deletions

36
.gitignore vendored
View File

@@ -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
View 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
View 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
View 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)
}
}