storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/logger/target/http/http.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2018, 2019 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package http 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "net/http" 26 "strings" 27 "time" 28 29 xhttp "storj.io/minio/cmd/http" 30 "storj.io/minio/cmd/logger" 31 ) 32 33 // Target implements logger.Target and sends the json 34 // format of a log entry to the configured http endpoint. 35 // An internal buffer of logs is maintained but when the 36 // buffer is full, new logs are just ignored and an error 37 // is returned to the caller. 38 type Target struct { 39 // Channel of log entries 40 logCh chan interface{} 41 42 name string 43 // HTTP(s) endpoint 44 endpoint string 45 // Authorization token for `endpoint` 46 authToken string 47 // User-Agent to be set on each log to `endpoint` 48 userAgent string 49 logKind string 50 client http.Client 51 } 52 53 // Endpoint returns the backend endpoint 54 func (h *Target) Endpoint() string { 55 return h.endpoint 56 } 57 58 func (h *Target) String() string { 59 return h.name 60 } 61 62 // Validate validate the http target 63 func (h *Target) Validate() error { 64 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 65 defer cancel() 66 67 req, err := http.NewRequestWithContext(ctx, http.MethodPost, h.endpoint, strings.NewReader(`{}`)) 68 if err != nil { 69 return err 70 } 71 72 req.Header.Set(xhttp.ContentType, "application/json") 73 74 // Set user-agent to indicate MinIO release 75 // version to the configured log endpoint 76 req.Header.Set("User-Agent", h.userAgent) 77 78 if h.authToken != "" { 79 req.Header.Set("Authorization", h.authToken) 80 } 81 82 resp, err := h.client.Do(req) 83 if err != nil { 84 return err 85 } 86 87 // Drain any response. 88 xhttp.DrainBody(resp.Body) 89 90 if resp.StatusCode != http.StatusOK { 91 switch resp.StatusCode { 92 case http.StatusForbidden: 93 return fmt.Errorf("%s returned '%s', please check if your auth token is correctly set", 94 h.endpoint, resp.Status) 95 } 96 return fmt.Errorf("%s returned '%s', please check your endpoint configuration", 97 h.endpoint, resp.Status) 98 } 99 100 return nil 101 } 102 103 func (h *Target) startHTTPLogger() { 104 // Create a routine which sends json logs received 105 // from an internal channel. 106 go func() { 107 for entry := range h.logCh { 108 logJSON, err := json.Marshal(&entry) 109 if err != nil { 110 continue 111 } 112 113 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 114 req, err := http.NewRequestWithContext(ctx, http.MethodPost, 115 h.endpoint, bytes.NewReader(logJSON)) 116 if err != nil { 117 cancel() 118 continue 119 } 120 req.Header.Set(xhttp.ContentType, "application/json") 121 122 // Set user-agent to indicate MinIO release 123 // version to the configured log endpoint 124 req.Header.Set("User-Agent", h.userAgent) 125 126 if h.authToken != "" { 127 req.Header.Set("Authorization", h.authToken) 128 } 129 130 resp, err := h.client.Do(req) 131 cancel() 132 if err != nil { 133 logger.LogOnceIf(ctx, fmt.Errorf("%s returned '%w', please check your endpoint configuration", 134 h.endpoint, err), h.endpoint) 135 continue 136 } 137 138 // Drain any response. 139 xhttp.DrainBody(resp.Body) 140 141 if resp.StatusCode != http.StatusOK { 142 switch resp.StatusCode { 143 case http.StatusForbidden: 144 logger.LogOnceIf(ctx, fmt.Errorf("%s returned '%s', please check if your auth token is correctly set", 145 h.endpoint, resp.Status), h.endpoint) 146 default: 147 logger.LogOnceIf(ctx, fmt.Errorf("%s returned '%s', please check your endpoint configuration", 148 h.endpoint, resp.Status), h.endpoint) 149 } 150 } 151 } 152 }() 153 } 154 155 // Option is a function type that accepts a pointer Target 156 type Option func(*Target) 157 158 // WithTargetName target name 159 func WithTargetName(name string) Option { 160 return func(t *Target) { 161 t.name = name 162 } 163 } 164 165 // WithEndpoint adds a new endpoint 166 func WithEndpoint(endpoint string) Option { 167 return func(t *Target) { 168 t.endpoint = endpoint 169 } 170 } 171 172 // WithLogKind adds a log type for this target 173 func WithLogKind(logKind string) Option { 174 return func(t *Target) { 175 t.logKind = strings.ToUpper(logKind) 176 } 177 } 178 179 // WithUserAgent adds a custom user-agent sent to the target. 180 func WithUserAgent(userAgent string) Option { 181 return func(t *Target) { 182 t.userAgent = userAgent 183 } 184 } 185 186 // WithAuthToken adds a new authorization header to be sent to target. 187 func WithAuthToken(authToken string) Option { 188 return func(t *Target) { 189 t.authToken = authToken 190 } 191 } 192 193 // WithTransport adds a custom transport with custom timeouts and tuning. 194 func WithTransport(transport *http.Transport) Option { 195 return func(t *Target) { 196 t.client = http.Client{ 197 Transport: transport, 198 } 199 } 200 } 201 202 // New initializes a new logger target which 203 // sends log over http to the specified endpoint 204 func New(opts ...Option) *Target { 205 h := &Target{ 206 logCh: make(chan interface{}, 10000), 207 } 208 209 // Loop through each option 210 for _, opt := range opts { 211 // Call the option giving the instantiated 212 // *Target as the argument 213 opt(h) 214 } 215 216 h.startHTTPLogger() 217 return h 218 } 219 220 // Send log message 'e' to http target. 221 func (h *Target) Send(entry interface{}, errKind string) error { 222 if h.logKind != errKind && h.logKind != "ALL" { 223 return nil 224 } 225 226 select { 227 case h.logCh <- entry: 228 default: 229 // log channel is full, do not wait and return 230 // an error immediately to the caller 231 return errors.New("log buffer full") 232 } 233 234 return nil 235 }