1
+ export class LokiClient {
2
+ private readonly lokiUrl : string ;
3
+ private readonly lokiApiKey : string ;
4
+ private readonly environment : string ;
5
+ user : string ;
6
+
7
+ constructor ( lokiUrl : string , lokiApiKey : string , user : string , environment : string ) {
8
+ this . lokiUrl = lokiUrl ;
9
+ this . lokiApiKey = lokiApiKey ;
10
+ this . environment = environment ;
11
+ this . user = user ;
12
+ }
13
+
14
+ queryError ( service : string ) : string {
15
+ return `{app="${ service } ", env="${ this . environment } "} |= "error"` ;
16
+ }
17
+
18
+ async getLogCountByLevel ( app : string , rangeMinutes : number ) : Promise < any > {
19
+ const query = `sum by (detected_level) (count_over_time({app="${ app } ", env="${ this . environment } "}[5m]))` ;
20
+ const end = new Date ( ) ;
21
+ const start = new Date ( end . getTime ( ) - rangeMinutes * 60 * 1000 ) ;
22
+ const data = await this . queryLoki ( query , start . toISOString ( ) , end . toISOString ( ) ) ;
23
+ return data ;
24
+ }
25
+
26
+ async getAllEnvironments ( ) : Promise < string [ ] > {
27
+ return this . getAllValuesForLabel ( 'env' ) ;
28
+ }
29
+
30
+
31
+ async getAllApps ( ) : Promise < string [ ] > {
32
+ return this . getAllValuesForLabel ( 'app' ) ;
33
+ }
34
+
35
+ /**
36
+ * This function gets all available values for a label in Loki.
37
+ * @returns
38
+ */
39
+ async getAllValuesForLabel ( label : string ) : Promise < string [ ] > {
40
+ const url = new URL ( `${ this . lokiUrl } /loki/api/v1/label/${ label } /values` ) ;
41
+ const authHeader = 'Basic ' + btoa ( `${ this . user } :${ this . lokiApiKey } ` ) ;
42
+
43
+ const response = await fetch ( url . toString ( ) , {
44
+ method : 'GET' ,
45
+ headers : {
46
+ 'Content-Type' : 'application/json' ,
47
+ 'Authorization' : authHeader
48
+ }
49
+ } ) ;
50
+
51
+ if ( ! response . ok ) {
52
+ throw new Error ( `Error fetching available services: ${ response . statusText } ` ) ;
53
+ }
54
+
55
+ const data = await response . json ( ) ;
56
+ return data . data ; // Assuming the response structure is { "status": "success", "data": ["app1", "app2", ...] }
57
+ }
58
+
59
+ async getErrorsForService ( service : string , rangeMinutes : number ) {
60
+ // Get the current time and subtract "rangeMinutes" minutes
61
+ const end = new Date ( ) ;
62
+ const start = new Date ( end . getTime ( ) - rangeMinutes * 60 * 1000 ) ;
63
+
64
+ // Convert to ISO string format
65
+ const startISOString = start . toISOString ( ) ;
66
+ const endISOString = end . toISOString ( ) ;
67
+ const query = this . queryError ( service ) ;
68
+ return this . queryLoki ( query , startISOString , endISOString ) ;
69
+ }
70
+ async queryLoki ( query : string , start : string , end : string ) : Promise < any > {
71
+ const url = new URL ( `${ this . lokiUrl } /loki/api/v1/query_range` ) ;
72
+ url . searchParams . append ( 'query' , query ) ;
73
+ url . searchParams . append ( 'start' , start ) ;
74
+ url . searchParams . append ( 'end' , end ) ;
75
+ const authHeader = 'Basic ' + btoa ( `${ this . user } :${ this . lokiApiKey } ` ) ;
76
+
77
+ const response = await fetch ( url . toString ( ) , {
78
+ method : 'GET' ,
79
+ headers : {
80
+ 'Content-Type' : 'application/json' ,
81
+ Authorization : authHeader ,
82
+ } ,
83
+ } ) ;
84
+
85
+ if ( ! response . ok ) {
86
+ const errorText = await response . text ( ) ;
87
+ throw new Error (
88
+ `Error querying Loki: ${ response . status } ${ response . statusText } - ${ errorText } ` ,
89
+ ) ;
90
+ }
91
+ //https://grafana.com/docs/loki/latest/reference/loki-http-api/#query-logs-within-a-range-of-time
92
+ return response . json ( ) ;
93
+ }
94
+ }
0 commit comments