@myunisoft/loki
Version:
Node.js Grafana Loki SDK
313 lines (307 loc) • 8.4 kB
JavaScript
// 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
};