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  }