github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/integration/client/client.go (about)

     1  package client
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"net/url"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  )
    15  
    16  type roundTripper struct {
    17  	instanceID    string
    18  	token         string
    19  	injectHeaders map[string][]string
    20  	next          http.RoundTripper
    21  }
    22  
    23  func (r *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
    24  	req.Header.Set("X-Scope-OrgID", r.instanceID)
    25  	if r.token != "" {
    26  		req.SetBasicAuth(r.instanceID, r.token)
    27  	}
    28  
    29  	for key, values := range r.injectHeaders {
    30  		for _, v := range values {
    31  			req.Header.Add(key, v)
    32  		}
    33  
    34  		fmt.Println(req.Header.Values(key))
    35  	}
    36  
    37  	return r.next.RoundTrip(req)
    38  }
    39  
    40  type Option interface {
    41  	Type() string
    42  }
    43  
    44  type InjectHeadersOption map[string][]string
    45  
    46  func (n InjectHeadersOption) Type() string {
    47  	return "headerinject"
    48  }
    49  
    50  // Client is a HTTP client that adds basic auth and scope
    51  type Client struct {
    52  	Now time.Time
    53  
    54  	httpClient *http.Client
    55  	baseURL    string
    56  	instanceID string
    57  }
    58  
    59  // NewLogsClient creates a new client
    60  func New(instanceID, token, baseURL string, opts ...Option) *Client {
    61  	rt := &roundTripper{
    62  		instanceID: instanceID,
    63  		token:      token,
    64  		next:       http.DefaultTransport,
    65  	}
    66  
    67  	for _, opt := range opts {
    68  		switch opt.Type() {
    69  		case "headerinject":
    70  			rt.injectHeaders = opt.(InjectHeadersOption)
    71  		}
    72  	}
    73  
    74  	return &Client{
    75  		Now: time.Now(),
    76  		httpClient: &http.Client{
    77  			Transport: rt,
    78  		},
    79  		baseURL:    baseURL,
    80  		instanceID: instanceID,
    81  	}
    82  }
    83  
    84  // PushLogLine creates a new logline with the current time as timestamp
    85  func (c *Client) PushLogLine(line string, extraLabels ...map[string]string) error {
    86  	return c.pushLogLine(line, c.Now, extraLabels...)
    87  }
    88  
    89  // PushLogLineWithTimestamp creates a new logline at the given timestamp
    90  // The timestamp has to be a Unix timestamp (epoch seconds)
    91  func (c *Client) PushLogLineWithTimestamp(line string, timestamp time.Time, extraLabelList ...map[string]string) error {
    92  	return c.pushLogLine(line, timestamp, extraLabelList...)
    93  }
    94  
    95  func formatTS(ts time.Time) string {
    96  	return strconv.FormatInt(ts.UnixNano(), 10)
    97  }
    98  
    99  type stream struct {
   100  	Stream map[string]string `json:"stream"`
   101  	Values [][]string        `json:"values"`
   102  }
   103  
   104  // pushLogLine creates a new logline
   105  func (c *Client) pushLogLine(line string, timestamp time.Time, extraLabelList ...map[string]string) error {
   106  	apiEndpoint := fmt.Sprintf("%s/loki/api/v1/push", c.baseURL)
   107  
   108  	s := stream{
   109  		Stream: map[string]string{
   110  			"job": "varlog",
   111  		},
   112  		Values: [][]string{
   113  			{
   114  				formatTS(timestamp),
   115  				line,
   116  			},
   117  		},
   118  	}
   119  	// add extra labels
   120  	for _, labelList := range extraLabelList {
   121  		for k, v := range labelList {
   122  			s.Stream[k] = v
   123  		}
   124  	}
   125  
   126  	data, err := json.Marshal(&struct {
   127  		Streams []stream `json:"streams"`
   128  	}{
   129  		Streams: []stream{s},
   130  	})
   131  	if err != nil {
   132  		return err
   133  	}
   134  	req, err := http.NewRequest("POST", apiEndpoint, bytes.NewReader(data))
   135  	if err != nil {
   136  		return err
   137  	}
   138  	req.Header.Set("Content-Type", "application/json")
   139  	req.Header.Set("X-Scope-OrgID", c.instanceID)
   140  
   141  	// Execute HTTP request
   142  	res, err := c.httpClient.Do(req)
   143  	if err != nil {
   144  		return err
   145  	}
   146  
   147  	if res.StatusCode/100 == 2 {
   148  		defer res.Body.Close()
   149  		return nil
   150  	}
   151  
   152  	buf, err := io.ReadAll(res.Body)
   153  	if err != nil {
   154  		return fmt.Errorf("reading request failed with status code %v: %w", res.StatusCode, err)
   155  	}
   156  
   157  	return fmt.Errorf("request failed with status code %v: %w", res.StatusCode, errors.New(string(buf)))
   158  }
   159  
   160  func (c *Client) Get(path string) (*http.Response, error) {
   161  	url := fmt.Sprintf("%s%s", c.baseURL, path)
   162  	req, err := http.NewRequest("GET", url, nil)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  	return c.httpClient.Do(req)
   167  }
   168  
   169  // Get all the metrics
   170  func (c *Client) Metrics() (string, error) {
   171  	url := fmt.Sprintf("%s/metrics", c.baseURL)
   172  	res, err := http.Get(url)
   173  	if err != nil {
   174  		return "", err
   175  	}
   176  
   177  	var sb strings.Builder
   178  	if _, err := io.Copy(&sb, res.Body); err != nil {
   179  		return "", err
   180  	}
   181  
   182  	if res.StatusCode != http.StatusOK {
   183  		return "", fmt.Errorf("request failed with status code %d", res.StatusCode)
   184  	}
   185  	return sb.String(), nil
   186  }
   187  
   188  // Flush all in-memory chunks held by the ingesters to the backing store
   189  func (c *Client) Flush() error {
   190  	req, err := c.request("POST", fmt.Sprintf("%s/flush", c.baseURL))
   191  	if err != nil {
   192  		return err
   193  	}
   194  
   195  	req.Header.Set("Content-Type", "application/json")
   196  
   197  	res, err := c.httpClient.Do(req)
   198  	if err != nil {
   199  		return err
   200  	}
   201  	defer res.Body.Close()
   202  
   203  	if res.StatusCode/100 == 2 {
   204  		return nil
   205  	}
   206  	return fmt.Errorf("request failed with status code %d", res.StatusCode)
   207  }
   208  
   209  type DeleteRequestParams struct {
   210  	Query string `json:"query"`
   211  	Start string `json:"start,omitempty"`
   212  	End   string `json:"end,omitempty"`
   213  }
   214  
   215  // AddDeleteRequest adds a new delete request
   216  func (c *Client) AddDeleteRequest(params DeleteRequestParams) error {
   217  	apiEndpoint := fmt.Sprintf("%s/loki/api/v1/delete", c.baseURL)
   218  
   219  	req, err := http.NewRequest("POST", apiEndpoint, nil)
   220  	if err != nil {
   221  		return err
   222  	}
   223  
   224  	q := req.URL.Query()
   225  	q.Add("query", params.Query)
   226  	q.Add("start", params.Start)
   227  	q.Add("end", params.End)
   228  	req.URL.RawQuery = q.Encode()
   229  	fmt.Printf("Delete request URL: %v\n", req.URL.String())
   230  
   231  	res, err := c.httpClient.Do(req)
   232  	if err != nil {
   233  		return err
   234  	}
   235  
   236  	if res.StatusCode != http.StatusNoContent {
   237  		buf, err := io.ReadAll(res.Body)
   238  		if err != nil {
   239  			return fmt.Errorf("reading request failed with status code %v: %w", res.StatusCode, err)
   240  		}
   241  		defer res.Body.Close()
   242  		return fmt.Errorf("request failed with status code %v: %w", res.StatusCode, errors.New(string(buf)))
   243  	}
   244  
   245  	return nil
   246  }
   247  
   248  type DeleteRequests []DeleteRequest
   249  type DeleteRequest struct {
   250  	RequestID string  `json:"request_id"`
   251  	StartTime float64 `json:"start_time"`
   252  	EndTime   float64 `json:"end_time"`
   253  	Query     string  `json:"query"`
   254  	Status    string  `json:"status"`
   255  	CreatedAt float64 `json:"created_at"`
   256  }
   257  
   258  // GetDeleteRequest gets a delete request using the request ID
   259  func (c *Client) GetDeleteRequests() (DeleteRequests, error) {
   260  	resp, err := c.Get("/loki/api/v1/delete")
   261  	if err != nil {
   262  		return nil, err
   263  	}
   264  	defer resp.Body.Close()
   265  
   266  	buf, err := io.ReadAll(resp.Body)
   267  	if err != nil {
   268  		return nil, fmt.Errorf("reading request failed with status code %v: %w", resp.StatusCode, err)
   269  	}
   270  
   271  	var deleteReqs DeleteRequests
   272  	err = json.Unmarshal(buf, &deleteReqs)
   273  	if err != nil {
   274  		return nil, fmt.Errorf("parsing json output failed: %w", err)
   275  	}
   276  
   277  	return deleteReqs, nil
   278  }
   279  
   280  // StreamValues holds a label key value pairs for the Stream and a list of a list of values
   281  type StreamValues struct {
   282  	Stream map[string]string
   283  	Values [][]string
   284  }
   285  
   286  // MatrixValues holds a label key value pairs for the metric and a list of a list of values
   287  type MatrixValues struct {
   288  	Metric map[string]string
   289  	Values [][]interface{}
   290  }
   291  
   292  // VectorValues holds a label key value pairs for the metric and single timestamp and value
   293  type VectorValues struct {
   294  	Metric map[string]string `json:"metric"`
   295  	Time   time.Time
   296  	Value  string
   297  }
   298  
   299  func (a *VectorValues) UnmarshalJSON(b []byte) error {
   300  	var s struct {
   301  		Metric map[string]string `json:"metric"`
   302  		Value  []interface{}     `json:"value"`
   303  	}
   304  	if err := json.Unmarshal(b, &s); err != nil {
   305  		return err
   306  	}
   307  	a.Metric = s.Metric
   308  	if len(s.Value) != 2 {
   309  		return fmt.Errorf("unexpected value length %d", len(s.Value))
   310  	}
   311  	if ts, ok := s.Value[0].(int64); ok {
   312  		a.Time = time.Unix(ts, 0)
   313  	}
   314  	if val, ok := s.Value[1].(string); ok {
   315  		a.Value = val
   316  	}
   317  	return nil
   318  }
   319  
   320  // DataType holds the result type and a list of StreamValues
   321  type DataType struct {
   322  	ResultType string
   323  	Stream     []StreamValues
   324  	Matrix     []MatrixValues
   325  	Vector     []VectorValues
   326  }
   327  
   328  func (a *DataType) UnmarshalJSON(b []byte) error {
   329  	// get the result type
   330  	var s struct {
   331  		ResultType string          `json:"resultType"`
   332  		Result     json.RawMessage `json:"result"`
   333  	}
   334  	if err := json.Unmarshal(b, &s); err != nil {
   335  		return err
   336  	}
   337  
   338  	switch s.ResultType {
   339  	case "streams":
   340  		if err := json.Unmarshal(s.Result, &a.Stream); err != nil {
   341  			return err
   342  		}
   343  	case "matrix":
   344  		if err := json.Unmarshal(s.Result, &a.Matrix); err != nil {
   345  			return err
   346  		}
   347  	case "vector":
   348  		if err := json.Unmarshal(s.Result, &a.Vector); err != nil {
   349  			return err
   350  		}
   351  	default:
   352  		return fmt.Errorf("unknown result type %s", s.ResultType)
   353  	}
   354  	a.ResultType = s.ResultType
   355  	return nil
   356  }
   357  
   358  // Response holds the status and data
   359  type Response struct {
   360  	Status string
   361  	Data   DataType
   362  }
   363  
   364  // RunRangeQuery runs a query and returns an error if anything went wrong
   365  func (c *Client) RunRangeQuery(query string) (*Response, error) {
   366  	buf, statusCode, err := c.run(c.rangeQueryURL(query))
   367  	if err != nil {
   368  		return nil, err
   369  	}
   370  
   371  	return c.parseResponse(buf, statusCode)
   372  }
   373  
   374  // RunQuery runs a query and returns an error if anything went wrong
   375  func (c *Client) RunQuery(query string) (*Response, error) {
   376  	v := url.Values{}
   377  	v.Set("query", query)
   378  	v.Set("time", formatTS(c.Now.Add(time.Second)))
   379  
   380  	u, err := url.Parse(c.baseURL)
   381  	if err != nil {
   382  		return nil, err
   383  	}
   384  	u.Path = "/loki/api/v1/query"
   385  	u.RawQuery = v.Encode()
   386  
   387  	buf, statusCode, err := c.run(u.String())
   388  	if err != nil {
   389  		return nil, err
   390  	}
   391  
   392  	return c.parseResponse(buf, statusCode)
   393  }
   394  
   395  func (c *Client) parseResponse(buf []byte, statusCode int) (*Response, error) {
   396  	lokiResp := Response{}
   397  	err := json.Unmarshal(buf, &lokiResp)
   398  	if err != nil {
   399  		return nil, fmt.Errorf("error parsing response data: %w", err)
   400  	}
   401  
   402  	if statusCode/100 == 2 {
   403  		return &lokiResp, nil
   404  	}
   405  	return nil, fmt.Errorf("request failed with status code %d: %w", statusCode, errors.New(string(buf)))
   406  }
   407  
   408  func (c *Client) rangeQueryURL(query string) string {
   409  	v := url.Values{}
   410  	v.Set("query", query)
   411  	v.Set("start", formatTS(c.Now.Add(-2*time.Hour)))
   412  	v.Set("end", formatTS(c.Now.Add(time.Second)))
   413  
   414  	u, err := url.Parse(c.baseURL)
   415  	if err != nil {
   416  		panic(err)
   417  	}
   418  	u.Path = "/loki/api/v1/query_range"
   419  	u.RawQuery = v.Encode()
   420  
   421  	return u.String()
   422  }
   423  
   424  func (c *Client) LabelNames() ([]string, error) {
   425  	url := fmt.Sprintf("%s/loki/api/v1/labels", c.baseURL)
   426  
   427  	req, err := c.request("GET", url)
   428  	if err != nil {
   429  		return nil, err
   430  	}
   431  
   432  	res, err := c.httpClient.Do(req)
   433  	if err != nil {
   434  		return nil, err
   435  	}
   436  	defer res.Body.Close()
   437  
   438  	if res.StatusCode/100 != 2 {
   439  		return nil, fmt.Errorf("Unexpected status code of %d", res.StatusCode)
   440  	}
   441  
   442  	var values struct {
   443  		Data []string `json:"data"`
   444  	}
   445  	if err := json.NewDecoder(res.Body).Decode(&values); err != nil {
   446  		return nil, err
   447  	}
   448  
   449  	return values.Data, nil
   450  }
   451  
   452  // LabelValues return a LabelValues query
   453  func (c *Client) LabelValues(labelName string) ([]string, error) {
   454  	url := fmt.Sprintf("%s/loki/api/v1/label/%s/values", c.baseURL, url.PathEscape(labelName))
   455  
   456  	req, err := c.request("GET", url)
   457  	if err != nil {
   458  		return nil, err
   459  	}
   460  
   461  	res, err := c.httpClient.Do(req)
   462  	if err != nil {
   463  		return nil, err
   464  	}
   465  	defer res.Body.Close()
   466  
   467  	if res.StatusCode/100 != 2 {
   468  		return nil, fmt.Errorf("Unexpected status code of %d", res.StatusCode)
   469  	}
   470  
   471  	var values struct {
   472  		Data []string `json:"data"`
   473  	}
   474  	if err := json.NewDecoder(res.Body).Decode(&values); err != nil {
   475  		return nil, err
   476  	}
   477  
   478  	return values.Data, nil
   479  }
   480  
   481  func (c *Client) request(method string, url string) (*http.Request, error) {
   482  	req, err := http.NewRequest(method, url, nil)
   483  	if err != nil {
   484  		return nil, err
   485  	}
   486  	req.Header.Set("X-Scope-OrgID", c.instanceID)
   487  	return req, nil
   488  }
   489  
   490  func (c *Client) run(u string) ([]byte, int, error) {
   491  	req, err := c.request("GET", u)
   492  	if err != nil {
   493  		return nil, 0, err
   494  	}
   495  
   496  	// Execute HTTP request
   497  	res, err := c.httpClient.Do(req)
   498  	if err != nil {
   499  		return nil, 0, err
   500  	}
   501  	defer res.Body.Close()
   502  
   503  	buf, err := io.ReadAll(res.Body)
   504  	if err != nil {
   505  		return nil, 0, fmt.Errorf("request failed with status code %v: %w", res.StatusCode, err)
   506  	}
   507  
   508  	return buf, res.StatusCode, nil
   509  }