UNPKG

@myunisoft/loki

Version:

Node.js Grafana Loki SDK

313 lines (307 loc) 8.4 kB
// src/class/ApiCredential.class.ts var ApiCredential = class _ApiCredential { authorization; userAgent; static buildAuthorizationHeader(authorizationOptions) { switch (authorizationOptions.type) { case "bearer": return `Bearer ${authorizationOptions.token}`; case "classic": { const { username, password } = authorizationOptions; return Buffer.from(`${username}:${password}`).toString("base64"); } case "custom": default: return authorizationOptions.authorization; } } constructor(authorizationOptions, userAgent) { this.authorization = authorizationOptions ? _ApiCredential.buildAuthorizationHeader(authorizationOptions) : null; this.userAgent = userAgent ?? null; } get httpOptions() { return { headers: { ...this.authorization === null ? {} : { authorization: this.authorization }, ...this.userAgent === null ? {} : { "User-Agent": this.userAgent } } }; } }; // src/class/Datasources.class.ts import * as httpie from "@myunisoft/httpie"; var Datasources = class { remoteApiURL; credential; constructor(remoteApiURL, credential) { this.remoteApiURL = remoteApiURL; this.credential = credential; } async all() { const uri = new URL( "/api/datasources", this.remoteApiURL ); const { data } = await httpie.get( uri, this.credential.httpOptions ); return data; } async byId(id) { const uri = new URL( `/api/datasources/${id}`, this.remoteApiURL ); const { data } = await httpie.get( uri, this.credential.httpOptions ); return data; } async byName(name) { const uri = new URL( `/api/datasources/name/${name}`, this.remoteApiURL ); const { data } = await httpie.get( uri, this.credential.httpOptions ); return data; } async byUID(uid) { const uri = new URL( `/api/datasources/uid/${uid}`, this.remoteApiURL ); const { data } = await httpie.get( uri, this.credential.httpOptions ); return data; } async idByName(name) { const uri = new URL( `/api/datasources/id/${name}`, this.remoteApiURL ); const { data } = await httpie.get( uri, this.credential.httpOptions ); return data.id; } }; // src/class/Loki.class.ts import * as httpie2 from "@myunisoft/httpie"; import autoURL from "@openally/auto-url"; import { LogQL, StreamSelector } from "@sigyn/logql"; import { Pattern, NoopPattern } from "@sigyn/pattern"; // src/utils.ts import dayjs from "dayjs"; import ms from "ms"; function durationToUnixTimestamp(duration) { if (typeof duration === "number") { return String(duration); } if (!Object.is(Number(duration), NaN)) { return duration; } return dayjs().subtract(ms(duration), "milliseconds").unix().toString(); } function transformStreamOrMatrixValue(value) { const [unixEpoch, log] = value; return { date: dayjs.unix(Number(unixEpoch) / 1e6), log }; } function inlineLogs(result) { if (result.status !== "success") { return null; } const flatLogs = result.data.result.flatMap( (host) => host.values.map(transformStreamOrMatrixValue) ).sort((left, right) => left.date.isBefore(right.date) ? 1 : -1); if (flatLogs.length === 0) { return null; } return { values: flatLogs.map((row) => row.log), timerange: [flatLogs.at(0).date.unix(), flatLogs.at(-1).date.unix()] }; } function streamOrMatrixTimeRange(result) { if (result.length === 0) { return null; } const flatLogs = result.flatMap( (host) => [ transformStreamOrMatrixValue(host.values.at(0)), transformStreamOrMatrixValue(host.values.at(-1)) ] ).sort((left, right) => left.date.isBefore(right.date) ? 1 : -1); return [ flatLogs.at(0).date.unix(), flatLogs.at(-1).date.unix() ]; } // src/class/Loki.class.ts function kDurationTransformer(value) { return durationToUnixTimestamp(value); } var kAutoURLGrafanaTransformer = { start: kDurationTransformer, end: kDurationTransformer }; var Loki = class { remoteApiURL; credential; constructor(remoteApiURL, credential) { this.remoteApiURL = remoteApiURL; this.credential = credential; } #fetchQueryRange(logQL, options = {}) { const { limit = 100 } = options; const query = typeof logQL === "string" ? logQL : logQL.toString(); const uri = autoURL( new URL("loki/api/v1/query_range", this.remoteApiURL), { ...options, limit, query }, kAutoURLGrafanaTransformer ); return httpie2.get( uri, this.credential.httpOptions ); } async queryRangeMatrix(logQL, options = {}) { if (LogQL.type(logQL) === "query") { throw new Error("Log queries must use `queryRangeStream` method"); } const { data } = await this.#fetchQueryRange( logQL, options ); return { metrics: data.data.result.map((result) => { return { labels: result.metric, values: result.values }; }), timerange: streamOrMatrixTimeRange(data.data.result) }; } async queryRangeStream(logQL, options = {}) { if (LogQL.type(logQL) === "metric") { throw new Error("Metric queries must use `queryRangeMatrix` method"); } const { pattern = new NoopPattern() } = options; const parser = pattern instanceof NoopPattern ? pattern : new Pattern(pattern); const { data } = await this.#fetchQueryRange( logQL, options ); return { streams: data.data.result.map((result) => { return { labels: result.stream, values: result.values.map(([unixEpoch, log]) => [unixEpoch, ...parser.executeOnLogs([log])]).filter((log) => log.length > 1) }; }), timerange: streamOrMatrixTimeRange(data.data.result) }; } async queryRange(logQL, options = {}) { const { pattern = new NoopPattern() } = options; const parser = pattern instanceof NoopPattern ? pattern : new Pattern(pattern); const { data } = await this.#fetchQueryRange(logQL, options); const inlinedLogs = inlineLogs(data); if (inlinedLogs === null) { return { logs: [], timerange: null }; } return { logs: parser.executeOnLogs(inlinedLogs.values), timerange: inlinedLogs.timerange }; } async labels(options = {}) { const uri = autoURL( new URL("loki/api/v1/labels", this.remoteApiURL), options, kAutoURLGrafanaTransformer ); const { data: labels } = await httpie2.get( uri, this.credential.httpOptions ); return labels.status === "success" ? labels.data : []; } async labelValues(label, options = {}) { const uri = autoURL( new URL(`loki/api/v1/label/${label}/values`, this.remoteApiURL), options, kAutoURLGrafanaTransformer ); const { data: labelValues } = await httpie2.get( uri, this.credential.httpOptions ); return labelValues.status === "success" ? labelValues.data : []; } async series(...match) { const uri = new URL(`loki/api/v1/series`, this.remoteApiURL); const { data: listSeries } = await httpie2.get(uri, { querystring: new URLSearchParams( match.map((selector) => ["match", new StreamSelector(selector).toString()]) ) }); return listSeries.status === "success" ? listSeries.data : []; } async push(logs) { const uri = new URL("loki/api/v1/push", this.remoteApiURL); const { headers } = this.credential.httpOptions; await httpie2.post(uri, { body: { streams: Array.from(logs) }, headers: { ...headers, "Content-Type": "application/json" } }); } }; // src/class/GrafanaApi.class.ts var GrafanaApi = class { remoteApiURL; credential; Datasources; Loki; constructor(options) { const { authentication, userAgent, remoteApiURL } = options; this.credential = new ApiCredential(authentication, userAgent); this.remoteApiURL = typeof remoteApiURL === "string" ? new URL(remoteApiURL) : remoteApiURL; this.Datasources = new Datasources( this.remoteApiURL, this.credential ); this.Loki = new Loki( this.remoteApiURL, this.credential ); } }; export { GrafanaApi };