github.com/avenga/couper@v1.12.2/definitions/job.go (about) 1 package definitions 2 3 import ( 4 "context" 5 "net/http" 6 "time" 7 8 "github.com/hashicorp/hcl/v2" 9 "github.com/sirupsen/logrus" 10 11 "github.com/avenga/couper/config" 12 "github.com/avenga/couper/config/request" 13 "github.com/avenga/couper/errors" 14 "github.com/avenga/couper/eval" 15 "github.com/avenga/couper/eval/variables" 16 "github.com/avenga/couper/handler/middleware" 17 "github.com/avenga/couper/logging" 18 "github.com/avenga/couper/server/writer" 19 "github.com/avenga/couper/utils" 20 ) 21 22 type Job struct { 23 conf *config.Job 24 handler http.Handler 25 interval time.Duration 26 settings *config.Settings 27 } 28 29 type Jobs []*Job 30 31 func (j Jobs) Run(ctx context.Context, log *logrus.Entry) { 32 if len(j) == 0 { 33 return 34 } 35 36 logEntry := log.WithContext(ctx) 37 logEntry.Data["type"] = "couper_job" 38 39 for _, job := range j { 40 go job.Run(ctx, logEntry) 41 } 42 } 43 44 func NewJob(j *config.Job, h http.Handler, settings *config.Settings) *Job { 45 return &Job{ 46 conf: j, 47 handler: h, 48 interval: j.IntervalDuration, 49 settings: settings, 50 } 51 } 52 53 func (j *Job) Run(ctx context.Context, logEntry *logrus.Entry) { 54 req, _ := http.NewRequest(http.MethodGet, "", nil) 55 req.Header.Set("User-Agent", "Couper / "+utils.VersionName+" job-"+j.conf.Name) 56 57 uidFn := middleware.NewUIDFunc(j.settings.RequestIDBackendHeader) 58 59 t := time.NewTicker(time.Millisecond * 50) 60 defer t.Stop() 61 62 firstRun := true 63 64 clh := middleware.NewCustomLogsHandler([]hcl.Body{j.conf.Remain}, j.handler, j.conf.Name) 65 66 for { 67 select { 68 case <-ctx.Done(): 69 logEntry.WithFields(logrus.Fields{ 70 "name": j.conf.Name, 71 }).Errorf("stopping: %v", ctx.Err()) 72 return 73 case <-t.C: 74 uid := uidFn() 75 76 outReq := req.Clone(context.WithValue(ctx, request.UID, uid)) 77 78 evalCtx := eval.ContextFromRequest(outReq).WithClientRequest(outReq) // setup syncMap, upstream custom logs 79 delete(evalCtx.HCLContext().Variables, variables.ClientRequest) // this is the noop req from above, not helpful 80 81 outCtx := context.WithValue(evalCtx, request.LogEntry, logEntry) 82 outCtx = context.WithValue(outCtx, request.LogCustomAccess, []hcl.Body{j.conf.Remain}) // local custom logs 83 outReq = outReq.WithContext(outCtx) 84 85 n := time.Now() 86 log := logEntry. 87 WithFields(logrus.Fields{ 88 "name": j.conf.Name, 89 "timings": logging.Fields{ 90 "total": logging.RoundMS(time.Since(n)), 91 "interval": logging.RoundMS(j.interval), 92 }, 93 "uid": uid, 94 }).WithContext(outCtx). 95 WithTime(n) 96 97 go run(outReq, clh, log) 98 99 if firstRun { 100 t.Reset(j.interval) 101 firstRun = false 102 } 103 } 104 } 105 } 106 107 func run(req *http.Request, h http.Handler, log *logrus.Entry) { 108 w := writer.NewResponseWriter(&noopResponseWriter{}, "") 109 h.ServeHTTP(w, req) 110 111 if w.StatusCode() == 0 || w.StatusCode() > 499 { 112 if ctxErr, ok := req.Context().Value(request.Error).(*errors.Error); ok { 113 if len(ctxErr.Kinds()) > 0 { 114 log = log.WithFields(logrus.Fields{"error_type": ctxErr.Kinds()[0]}) 115 } 116 log.Error(ctxErr.Error()) 117 return 118 } 119 log.Error() 120 return 121 } 122 log.Info() 123 } 124 125 var _ http.ResponseWriter = &noopResponseWriter{} 126 127 type noopResponseWriter struct { 128 header http.Header 129 } 130 131 func (n *noopResponseWriter) Header() http.Header { 132 if n.header == nil { 133 n.header = make(http.Header) 134 } 135 return n.header 136 } 137 138 func (n *noopResponseWriter) Write(bytes []byte) (int, error) { 139 return len(bytes), nil 140 } 141 142 func (n *noopResponseWriter) WriteHeader(_ int) {}