Skip to content

Commit

Permalink
queryRangeMatrix and queryRangeStream error (#171)
Browse files Browse the repository at this point in the history
* chore: update @sigyn/logql (2.1.0 to 2.2.0)

* feat: throw Errors for Matrix & Stream when using the wrong kind of metric
  • Loading branch information
fraxken authored Jul 2, 2024
1 parent d0c29c5 commit ec55fe4
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 54 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"dependencies": {
"@myunisoft/httpie": "^5.0.0",
"@openally/auto-url": "^1.0.1",
"@sigyn/logql": "^2.0.0",
"@sigyn/logql": "^2.2.0",
"@sigyn/pattern": "^1.0.0",
"dayjs": "^1.11.10",
"ms": "^2.1.3"
Expand Down
8 changes: 8 additions & 0 deletions src/class/Loki.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ export class Loki {
logQL: LogQL | string,
options: LokiQueryOptions<T> = {}
): Promise<QueryRangeMatrixResponse<T>> {
if (LogQL.type(logQL) === "query") {
throw new Error("Log queries must use `queryRangeStream` method");
}

const { pattern = new NoopPattern() } = options;
const parser: PatternShape<any> = pattern instanceof NoopPattern ?
pattern : new Pattern(pattern);
Expand All @@ -140,6 +144,10 @@ export class Loki {
logQL: LogQL | string,
options: LokiQueryOptions<T> = {}
): Promise<QueryRangeStreamResponse<T>> {
if (LogQL.type(logQL) === "metric") {
throw new Error("Metric queries must use `queryRangeMatrix` method");
}

const { pattern = new NoopPattern() } = options;
const parser: PatternShape<any> = pattern instanceof NoopPattern ?
pattern : new Pattern(pattern);
Expand Down
148 changes: 99 additions & 49 deletions test/Loki.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ const kMockAgent = new MockAgent();
kMockAgent.disableNetConnect();

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

before(() => {
setGlobalDispatcher(kMockAgent);
});

after(() => {
setGlobalDispatcher(kDefaultDispatcher);
});

describe("constructor", () => {
beforeEach(() => {
delete process.env.GRAFANA_API_TOKEN;
Expand All @@ -37,41 +47,77 @@ describe("GrafanaApi.Loki", () => {
});
});

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

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

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

it("should return expectedLogs with no modification (using NoopParser)", async() => {
it("should throw with a log query", async() => {
const sdk = new GrafanaApi({ remoteApiURL: kDummyURL });

await assert.rejects(
() => sdk.Loki.queryRangeMatrix("{app='foo'}"),
{
name: "Error",
message: "Log queries must use `queryRangeStream` method"
}
);
});

it("should return expectedLogs with no modification (NoopParser)", async() => {
const expectedLogs = ["hello world", "foobar"];

agentPoolInterceptor
.intercept({
path: (path) => path.includes("loki/api/v1/query_range")
})
.reply(200, mockStreamResponse(expectedLogs), {
.reply(200, mockMatrixResponse(expectedLogs), {
headers: { "Content-Type": "application/json" }
});

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

const result = await sdk.Loki.queryRange("{app='foo'}");
const result = await sdk.Loki.queryRangeMatrix("count_over_time({app='foo'} [5m])");
const resultLogs = result.logs[0]!;

assert.ok(
resultLogs.values.every((arr) => typeof arr[0] === "number")
);
assert.deepEqual(
result.values,
expectedLogs.slice(0).reverse()
resultLogs.values.map((arr) => arr[1]),
expectedLogs.slice(0)
);
assert.deepEqual(resultLogs.metric, { foo: "bar" });
});
});

it("should return expectedLogs with no modification (using NoopParser, queryRangeStream)", async() => {
const expectedLogs = ["hello world", "foobar"];
describe("queryRangeStream", () => {
before(() => {
process.env.GRAFANA_API_TOKEN = "";
});

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

it("should throw with a metric query", async() => {
const sdk = new GrafanaApi({ remoteApiURL: kDummyURL });

await assert.rejects(
() => sdk.Loki.queryRangeStream("count_over_time({app='foo'} [5m])"),
{
name: "Error",
message: "Metric queries must use `queryRangeMatrix` method"
}
);
});

it("should use the provided parser to transform logs", async() => {
const expectedLogs = ["hello 'Thomas'"];

agentPoolInterceptor
.intercept({
Expand All @@ -83,33 +129,33 @@ describe("GrafanaApi.Loki", () => {

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

const result = await sdk.Loki.queryRangeStream("{app='foo'}");
const result = await sdk.Loki.queryRangeStream("{app='foo'}", {
pattern: "hello '<name>'"
});
const resultLogs = result.logs[0]!;

assert.ok(
resultLogs.values.every((arr) => typeof arr[0] === "number")
);
assert.strictEqual(result.logs.length, 1);
assert.deepEqual(
resultLogs.values.map((arr) => arr[1]),
expectedLogs.slice(0)
resultLogs.values[0][1],
{ name: "Thomas" }
);
assert.deepEqual(resultLogs.stream, { foo: "bar" });
});

it("should return expectedLogs with no modification (using NoopParser, queryRangeMatrix)", async() => {
it("should return expectedLogs with no modification (using NoopParser)", async() => {
const expectedLogs = ["hello world", "foobar"];

agentPoolInterceptor
.intercept({
path: (path) => path.includes("loki/api/v1/query_range")
})
.reply(200, mockMatrixResponse(expectedLogs), {
.reply(200, mockStreamResponse(expectedLogs), {
headers: { "Content-Type": "application/json" }
});

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

const result = await sdk.Loki.queryRangeMatrix("{app='foo'}");
const result = await sdk.Loki.queryRangeStream("{app='foo'}");
const resultLogs = result.logs[0]!;

assert.ok(
Expand All @@ -119,10 +165,10 @@ describe("GrafanaApi.Loki", () => {
resultLogs.values.map((arr) => arr[1]),
expectedLogs.slice(0)
);
assert.deepEqual(resultLogs.metric, { foo: "bar" });
assert.deepEqual(resultLogs.stream, { foo: "bar" });
});

it("should return empty list of logs (using NoopParser, queryRangeStream)", async() => {
it("should return empty list of logs (using LogParser)", async() => {
const expectedLogs = [];

agentPoolInterceptor
Expand All @@ -135,16 +181,18 @@ describe("GrafanaApi.Loki", () => {

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

const result = await sdk.Loki.queryRangeStream("{app='foo'}");
const result = await sdk.Loki.queryRangeStream("{app='foo'}", {
pattern: "hello '<name>'"
});

assert.deepEqual(
result.logs,
expectedLogs
);
});

it("should use the provided parser to transform logs", async() => {
const expectedLogs = ["hello 'Thomas'"];
it("should return empty list of logs (using NoopParser)", async() => {
const expectedLogs = [];

agentPoolInterceptor
.intercept({
Expand All @@ -156,18 +204,26 @@ describe("GrafanaApi.Loki", () => {

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

const result = await sdk.Loki.queryRange("{app='foo'}", {
pattern: "hello '<name>'"
});
assert.strictEqual(result.values.length, 1);
const result = await sdk.Loki.queryRangeStream("{app='foo'}");

assert.deepEqual(
result.values[0],
{ name: "Thomas" }
result.logs,
expectedLogs
);
});
});

it("should use the provided parser to transform logs (queryRangeStream)", async() => {
const expectedLogs = ["hello 'Thomas'"];
describe("queryRange", () => {
before(() => {
process.env.GRAFANA_API_TOKEN = "";
});

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

it("should return expectedLogs with no modification (using NoopParser)", async() => {
const expectedLogs = ["hello world", "foobar"];

agentPoolInterceptor
.intercept({
Expand All @@ -179,21 +235,15 @@ describe("GrafanaApi.Loki", () => {

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

const result = await sdk.Loki.queryRangeStream("{app='foo'}", {
pattern: "hello '<name>'"
});
const resultLogs = result.logs[0]!;

assert.strictEqual(result.logs.length, 1);
const result = await sdk.Loki.queryRange("{app='foo'}");
assert.deepEqual(
resultLogs.values[0][1],
{ name: "Thomas" }
result.values,
expectedLogs.slice(0).reverse()
);
assert.deepEqual(resultLogs.stream, { foo: "bar" });
});

it("should return empty list of logs (using LogParser, queryRangeStream)", async() => {
const expectedLogs = [];
it("should use the provided parser to transform logs", async() => {
const expectedLogs = ["hello 'Thomas'"];

agentPoolInterceptor
.intercept({
Expand All @@ -205,13 +255,13 @@ describe("GrafanaApi.Loki", () => {

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

const result = await sdk.Loki.queryRangeStream("{app='foo'}", {
const result = await sdk.Loki.queryRange("{app='foo'}", {
pattern: "hello '<name>'"
});

assert.strictEqual(result.values.length, 1);
assert.deepEqual(
result.logs,
expectedLogs
result.values[0],
{ name: "Thomas" }
);
});
});
Expand Down

0 comments on commit ec55fe4

Please sign in to comment.