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 }