mirror of
https://github.com/hanzo-ml/kserve.git
synced 2026-10-30 20:53:49 +00:00
217 lines
6.1 KiB
Go
217 lines
6.1 KiB
Go
/*
|
|
Copyright 2021 The KServe Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package logger
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"slices"
|
|
"time"
|
|
|
|
"github.com/go-logr/logr"
|
|
guuid "github.com/google/uuid"
|
|
"knative.dev/pkg/network"
|
|
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
|
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
|
|
|
"github.com/kserve/kserve/pkg/apis/serving/v1beta1"
|
|
)
|
|
|
|
// loggingResponseWriter is a wrapper around an http.ResponseWriter that logs the response body
|
|
// It implements the http.ResponseWriter and http.Flusher interfaces
|
|
type loggingResponseWriter struct {
|
|
http.ResponseWriter // the original http.ResponseWriter
|
|
http.Flusher
|
|
statusCode int
|
|
responseBuffer *bytes.Buffer // buffer to store the response body for logging
|
|
log logr.Logger
|
|
}
|
|
|
|
func (w *loggingResponseWriter) Write(b []byte) (int, error) {
|
|
n, err := w.responseBuffer.Write(b)
|
|
if err != nil {
|
|
w.log.Error(err, "Failed to write response buffer")
|
|
return n, err
|
|
}
|
|
n, err = w.ResponseWriter.Write(b)
|
|
if err != nil {
|
|
w.log.Error(err, "Failed to write response")
|
|
return n, err
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func (w *loggingResponseWriter) WriteHeader(statusCode int) {
|
|
w.statusCode = statusCode
|
|
w.ResponseWriter.WriteHeader(statusCode)
|
|
}
|
|
|
|
func (w *loggingResponseWriter) Header() http.Header {
|
|
return w.ResponseWriter.Header()
|
|
}
|
|
|
|
func (w *loggingResponseWriter) Flush() {
|
|
if flusher, ok := w.ResponseWriter.(http.Flusher); ok {
|
|
flusher.Flush()
|
|
}
|
|
}
|
|
|
|
type LoggerHandler struct {
|
|
log logr.Logger
|
|
logUrl *url.URL
|
|
sourceUri *url.URL
|
|
logMode v1beta1.LoggerType
|
|
inferenceService string
|
|
namespace string
|
|
component string
|
|
endpoint string
|
|
next http.Handler
|
|
metadataHeaders []string
|
|
annotations map[string]string
|
|
certName string
|
|
tlsSkipVerify bool
|
|
}
|
|
|
|
func New(logUrl *url.URL, sourceUri *url.URL, logMode v1beta1.LoggerType,
|
|
inferenceService string, namespace string, endpoint string, component string, next http.Handler, metadataHeaders []string,
|
|
certName string, annotations map[string]string, tlsSkipVerify bool,
|
|
) http.Handler {
|
|
logf.SetLogger(zap.New())
|
|
return &LoggerHandler{
|
|
log: logf.Log.WithName("Logger"),
|
|
logUrl: logUrl,
|
|
sourceUri: sourceUri,
|
|
logMode: logMode,
|
|
inferenceService: inferenceService,
|
|
namespace: namespace,
|
|
component: component,
|
|
endpoint: endpoint,
|
|
next: next,
|
|
annotations: annotations,
|
|
metadataHeaders: metadataHeaders,
|
|
certName: certName,
|
|
tlsSkipVerify: tlsSkipVerify,
|
|
}
|
|
}
|
|
|
|
// call svc and add send request/responses to logUrl
|
|
func (eh *LoggerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
if network.IsKubeletProbe(r) {
|
|
if eh.next != nil {
|
|
eh.next.ServeHTTP(w, r)
|
|
}
|
|
return
|
|
}
|
|
// Record the time when the request arrives
|
|
requestTime := time.Now()
|
|
// Read request payload
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
http.Error(w, "can't read body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
metadata := map[string][]string{}
|
|
if eh.metadataHeaders != nil {
|
|
// Loop over header names
|
|
for name, values := range r.Header {
|
|
// Loop over all values for the name.
|
|
if slices.Contains(eh.metadataHeaders, name) {
|
|
metadata[name] = values
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get or Create an ID
|
|
id := getOrCreateID(r)
|
|
contentType := r.Header.Get("Content-Type")
|
|
// log Request
|
|
if eh.logMode == v1beta1.LogAll || eh.logMode == v1beta1.LogRequest {
|
|
if err := QueueLogRequest(LogRequest{
|
|
Url: eh.logUrl,
|
|
Bytes: &body,
|
|
ContentType: contentType,
|
|
ReqType: CEInferenceRequest,
|
|
Id: id,
|
|
SourceUri: eh.sourceUri,
|
|
InferenceService: eh.inferenceService,
|
|
Namespace: eh.namespace,
|
|
Endpoint: eh.endpoint,
|
|
Component: eh.component,
|
|
Annotations: eh.annotations,
|
|
Metadata: metadata,
|
|
CertName: eh.certName,
|
|
TlsSkipVerify: eh.tlsSkipVerify,
|
|
OccurrenceTime: requestTime,
|
|
}); err != nil {
|
|
eh.log.Error(err, "Failed to log request")
|
|
}
|
|
}
|
|
|
|
// Proxy Request
|
|
r.Body = io.NopCloser(bytes.NewBuffer(body))
|
|
// TODO: Set a reasonable initial buffer size
|
|
var responseBuf bytes.Buffer
|
|
lrw := &loggingResponseWriter{ResponseWriter: w, responseBuffer: &responseBuf, log: eh.log}
|
|
eh.next.ServeHTTP(lrw, r)
|
|
// Read the response body from the buffer
|
|
reader := bufio.NewReader(lrw.responseBuffer)
|
|
responseBody, err := io.ReadAll(reader)
|
|
if err != nil {
|
|
eh.log.Error(err, "Failed to read response body")
|
|
}
|
|
// Record the time when the response is received
|
|
responseTime := time.Now()
|
|
// log Response
|
|
if lrw.statusCode == http.StatusOK {
|
|
if eh.logMode == v1beta1.LogAll || eh.logMode == v1beta1.LogResponse {
|
|
if err := QueueLogRequest(LogRequest{
|
|
Url: eh.logUrl,
|
|
Bytes: &responseBody,
|
|
ContentType: contentType,
|
|
ReqType: CEInferenceResponse,
|
|
Id: id,
|
|
SourceUri: eh.sourceUri,
|
|
InferenceService: eh.inferenceService,
|
|
Namespace: eh.namespace,
|
|
Endpoint: eh.endpoint,
|
|
Annotations: eh.annotations,
|
|
Metadata: metadata,
|
|
Component: eh.component,
|
|
CertName: eh.certName,
|
|
TlsSkipVerify: eh.tlsSkipVerify,
|
|
OccurrenceTime: responseTime,
|
|
}); err != nil {
|
|
eh.log.Error(err, "Failed to log response")
|
|
}
|
|
}
|
|
} else {
|
|
eh.log.Info("Failed to proxy request", "status code", lrw.statusCode)
|
|
}
|
|
}
|
|
|
|
func getOrCreateID(r *http.Request) string {
|
|
id := r.Header.Get(CloudEventsIdHeader)
|
|
if id == "" {
|
|
id = guuid.New().String()
|
|
}
|
|
return id
|
|
}
|