MCPcopy Create free account
hub / github.com/ShipSecAI/studio / LokiLogClient

Class LokiLogClient

worker/src/adapters/loki-log.adapter.ts:25–81  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

23}
24
25export class LokiLogClient implements LokiPushClient {
26 constructor(private readonly config: LokiLogClientConfig) {}
27
28 async push(labels: Record<string, string>, lines: LokiStreamLine[]): Promise<void> {
29 if (lines.length === 0) {
30 return;
31 }
32
33 const url = this.resolveUrl('/loki/api/v1/push');
34 const headers: Record<string, string> = {
35 'Content-Type': 'application/json',
36 };
37
38 if (this.config.tenantId) {
39 headers['X-Scope-OrgID'] = this.config.tenantId;
40 }
41
42 if (this.config.username && this.config.password) {
43 const credentials = Buffer.from(`${this.config.username}:${this.config.password}`).toString(
44 'base64',
45 );
46 headers.Authorization = `Basic ${credentials}`;
47 }
48
49 const body = JSON.stringify({
50 streams: [
51 {
52 stream: labels,
53 values: lines.map((line) => [this.toNanoseconds(line.timestamp), line.message]),
54 },
55 ],
56 });
57
58 const response = await fetch(url, {
59 method: 'POST',
60 headers,
61 body,
62 });
63
64 if (!response.ok) {
65 const errorText = await response.text();
66 throw new ServiceError(`Loki push failed: ${errorText}`, {
67 statusCode: response.status,
68 details: { statusText: response.statusText, errorText },
69 });
70 }
71 }
72
73 private resolveUrl(path: string): string {
74 const base = this.config.baseUrl.replace(/\/+$/, '');
75 return `${base}${path}`;
76 }
77
78 private toNanoseconds(date: Date): string {
79 return (BigInt(date.getTime()) * 1000000n).toString();
80 }
81}
82

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected