Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add label endpoints #12

Merged
merged 1 commit into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export interface LokiQueryOptions<T> {

## API

### queryRange

You can provide a custom parser to queryRange (by default it inject a NoopParser doing nothing).

```ts
Expand Down Expand Up @@ -107,6 +109,82 @@ for (const data of logs) {

The parser will automatically escape and generate a RegExp with capture group (with a syntax similar to Loki pattern).

### labels

```ts
const labels = await api.labels();
```

`labels(options = {})` retrieves the list of known labels within a given time span. Loki may use a larger time span than the one specified. It accepts the following options:

```ts
interface LokiLabelsOptions {
/**
* The start time for the query as
* - a nanosecond Unix epoch.
* - a duration (i.e "2h")
*
* Default to 6 hours ago.
*/
start?: number | string;
/**
* The end time for the query as
* - a nanosecond Unix epoch.
* - a duration (i.e "2h")
*
* Default to now
*/
end?: number | string;
/**
* A duration used to calculate start relative to end. If end is in the future, start is calculated as this duration before now.
*
* Any value specified for start supersedes this parameter.
*/
since?: string;
}
```

### labelValues

```ts
const appLabelValues = await api.labelValues("app");
```

`labelValues(label, options = {})` retrieves the list of known values for a given label within a given time span. Loki may use a larger time span than the one specified.

```ts
interface LokiLabelValueOptions {
/**
* The start time for the query as
* - a nanosecond Unix epoch.
* - a duration (i.e "2h")
*
* Default to 6 hours ago.
*/
start?: number | string;
/**
* The end time for the query as
* - a nanosecond Unix epoch.
* - a duration (i.e "2h")
*
* Default to now
*/
end?: number | string;
/**
* A duration used to calculate start relative to end. If end is in the future, start is calculated as this duration before now.
*
* Any value specified for start supersedes this parameter.
*/
since?: string;
/**
* A set of log stream selector that selects the streams to match and return label values for <name>.
*
* Example: {"app": "myapp", "environment": "dev"}
*/
query?: string;
}
```

## Contributors ✨

<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
Expand Down
77 changes: 76 additions & 1 deletion src/class/GrafanaLoki.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as httpie from "@myunisoft/httpie";

// Import Internal Dependencies
import * as utils from "../utils.js";
import { QueryRangeResponse } from "../types.js";
import { LabelResponse, LabelValuesResponse, QueryRangeResponse } from "../types.js";
import { NoopLogParser, LogParserLike } from "./LogParser.class.js";

export interface LokiQueryOptions<T> {
Expand All @@ -28,6 +28,40 @@ export interface GrafanaLokiConstructorOptions {
remoteApiURL: string | URL;
}

export interface LokiLabelsOptions {
/**
* The start time for the query as
* - a nanosecond Unix epoch.
* - a duration (i.e "2h")
*
* Default to 6 hours ago.
*/
start?: number | string;
/**
* The end time for the query as
* - a nanosecond Unix epoch.
* - a duration (i.e "2h")
*
* Default to now
*/
end?: number | string;
/**
* A duration used to calculate start relative to end. If end is in the future, start is calculated as this duration before now.
*
* Any value specified for start supersedes this parameter.
*/
since?: string;
}

export interface LokiLabelValuesOptions extends LokiLabelsOptions {
/**
* A set of log stream selector that selects the streams to match and return label values for <name>.
*
* Example: {"app": "myapp", "environment": "dev"}
*/
query?: string;
}

export class GrafanaLoki {
private apiToken: string;
private remoteApiURL: URL;
Expand Down Expand Up @@ -84,4 +118,45 @@ export class GrafanaLoki {
utils.inlineLogs(data)
);
}

async labels(options: LokiLabelsOptions = {}): Promise<string[]> {
const uri = new URL("loki/api/v1/labels", this.remoteApiURL);
if (options.start) {
uri.searchParams.set("start", utils.durationToUnixTimestamp(options.start));
}
if (options.end) {
uri.searchParams.set("end", utils.durationToUnixTimestamp(options.end));
}
if (options.since) {
uri.searchParams.set("since", options.since);
}

const { data: labels } = await httpie.get<LabelResponse>(
uri, this.httpOptions
);

return labels.data;
}

async labelValues(label: string, options: LokiLabelValuesOptions = {}): Promise<string[]> {
const uri = new URL(`loki/api/v1/label/${label}/values`, this.remoteApiURL);
if (options.start) {
uri.searchParams.set("start", utils.durationToUnixTimestamp(options.start));
}
if (options.end) {
uri.searchParams.set("end", utils.durationToUnixTimestamp(options.end));
}
if (options.since) {
uri.searchParams.set("since", options.since);
}
if (options.query) {
uri.searchParams.set("query", options.query);
}

const { data: labelValues } = await httpie.get<LabelValuesResponse>(
uri, this.httpOptions
);

return labelValues.data;
}
}
10 changes: 10 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,13 @@ export interface QueryRangeResponse {
};
}
}

export interface LabelResponse {
status: "success";
data: string[];
}

export interface LabelValuesResponse {
status: "success";
data: string[];
}
62 changes: 61 additions & 1 deletion test/GrafanaLoki.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { MockAgent, setGlobalDispatcher, getGlobalDispatcher } from "@myunisoft/
// Import Internal Dependencies
import { GrafanaLoki } from "../src/class/GrafanaLoki.class.js";
import { LogParser } from "../src/class/LogParser.class.js";
import { QueryRangeResponse } from "../src/types.js";
import { LabelResponse, QueryRangeResponse } from "../src/types.js";

// CONSTANTS
const kDummyURL = "https://nodejs.org";
Expand Down Expand Up @@ -115,6 +115,59 @@ describe("GrafanaLoki", () => {
);
});
});

describe("labels", () => {
const agentPoolInterceptor = kMockAgent.get(kDummyURL);

before(() => {
process.env.GRAFANA_API_TOKEN = "";
setGlobalDispatcher(kMockAgent);
});

after(() => {
delete process.env.GRAFANA_API_TOKEN;
setGlobalDispatcher(kDefaultDispatcher);
});

it("should return labels", async() => {
const expectedLabels = ["app", "env"];

agentPoolInterceptor
.intercept({
path: (path) => path.includes("loki/api/v1/labels")
})
.reply(200, mockLabelResponse(expectedLabels), {
headers: { "Content-Type": "application/json" }
});

const sdk = new GrafanaLoki({ remoteApiURL: kDummyURL });

const result = await sdk.labels();
assert.deepEqual(
result,
expectedLabels
);
});

it("should return label values", async() => {
const expectedLabelValues = ["prod", "preprod"];
agentPoolInterceptor
.intercept({
path: (path) => path.includes("loki/api/v1/label/env/values")
})
.reply(200, mockLabelResponse(expectedLabelValues), {
headers: { "Content-Type": "application/json" }
});

const sdk = new GrafanaLoki({ remoteApiURL: kDummyURL });

const result = await sdk.labelValues("env");
assert.deepEqual(
result,
expectedLabelValues
);
});
});
});

type DeepPartial<T> = T extends object ? {
Expand All @@ -137,6 +190,13 @@ function mockStreamResponse(logs: string[]): DeepPartial<QueryRangeResponse> {
};
}

function mockLabelResponse(response: string[]): LabelResponse {
return {
status: "success",
data: response
};
}

function getNanoSecTime() {
const hrTime = process.hrtime();

Expand Down