Skip to content

Commit f4082bf

Browse files
Merge pull request #172 from alanconway/fix-log-queries
COO-1271: fix: Failed to open the Loki log through Korrel8r graph in OpenShift Console
2 parents f8ab86d + df6697c commit f4082bf

3 files changed

Lines changed: 75 additions & 39 deletions

File tree

web/src/__tests__/log.spec.ts

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -74,30 +74,56 @@ describe('LogDomain.queryToLink', () => {
7474
{
7575
// LogQL query
7676
query: `log:infrastructure:{kubernetes_namespace_name="default",kubernetes_pod_name="foo"}`,
77-
constraint: Constraint.fromAPI({
78-
start: '2025-03-25T10:00:00.000Z',
79-
end: '2025-03-25T22:00:00.000Z',
80-
}),
81-
url: new URIRef(`monitoring/logs`, {
82-
q: '{kubernetes_namespace_name="default",kubernetes_pod_name="foo"}',
83-
tenant: 'infrastructure',
84-
start: 1742896800000,
85-
end: 1742940000000,
86-
}),
77+
q: '{kubernetes_namespace_name="default",kubernetes_pod_name="foo"}',
78+
tenant: 'infrastructure',
8779
},
8880
{
8981
// k8s Pod query
9082
query: 'log:infrastructure:{"namespace":"default","name":"foo"}',
91-
constraint: Constraint.fromAPI({
83+
q: '{kubernetes_namespace_name="default",kubernetes_pod_name="foo"}',
84+
tenant: 'infrastructure',
85+
},
86+
{
87+
// k8s Pod query with labels
88+
query: 'log:infrastructure:{"namespace":"default","name":"foo","labels":{"a":"b","c":"d"}}',
89+
q: '{kubernetes_namespace_name="default",kubernetes_pod_name="foo"}|json|kubernetes_labels_a="b"|kubernetes_labels_c="d"',
90+
tenant: 'infrastructure',
91+
},
92+
{
93+
// k8s partial query
94+
query: 'log:infrastructure:{"namespace":"default","labels":{}}',
95+
q: '{kubernetes_namespace_name="default"}',
96+
tenant: 'infrastructure',
97+
},
98+
{
99+
// k8s partial query
100+
query:
101+
'log:infrastructure:{"namespace":"openshift-monitoring","labels":{"app":"cluster-monitoring-operator"}}',
102+
q: '{kubernetes_namespace_name="openshift-monitoring"}|json|kubernetes_labels_app="cluster-monitoring-operator"',
103+
tenant: 'infrastructure',
104+
},
105+
106+
{
107+
query: 'log:application:{}',
108+
q: '{}',
109+
tenant: 'application',
110+
},
111+
])('$query', ({ query, q, tenant }) => {
112+
const got = new LogDomain().queryToLink(
113+
Query.parse(query),
114+
Constraint.fromAPI({
92115
start: '2025-03-25T10:00:00.000Z',
93116
end: '2025-03-25T22:00:00.000Z',
94117
}),
95-
96-
url: new URIRef('k8s/ns/default/core~v1~Pod/foo/aggregated-logs'),
97-
},
98-
])('$query', ({ url, query, constraint }) =>
99-
expect(new LogDomain().queryToLink(Query.parse(query), constraint)).toEqual(url),
100-
);
118+
);
119+
const want = new URIRef('monitoring/logs', {
120+
q,
121+
tenant,
122+
start: 1742896800000,
123+
end: 1742940000000,
124+
});
125+
expect(got.toString()).toEqual(want.toString());
126+
});
101127
});
102128

103129
describe('expected errors', () => {

web/src/korrel8r/k8s.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type Model = {
1111
};
1212

1313
// Parsed form of a k8s query selector.
14-
type Selector = {
14+
export type Selector = {
1515
name?: string;
1616
namespace?: string;
1717
labels?: { [key: string]: string };

web/src/korrel8r/log.ts

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { K8sDomain } from './k8s';
2-
import { Class, Constraint, Domain, joinPath, Query, unixMilliseconds, URIRef } from './types';
1+
import { Class, Constraint, Domain, Query, unixMilliseconds, URIRef } from './types';
32

43
enum LogClass {
54
application = 'application',
@@ -10,13 +9,8 @@ enum LogClass {
109
// TODO: Aggregated log links and k8s:pod style log queries ignore the containers parameter.
1110

1211
export class LogDomain extends Domain {
13-
private k8s: K8sDomain;
14-
private pod: Class;
15-
1612
constructor() {
1713
super('log');
18-
this.k8s = new K8sDomain();
19-
this.pod = new Class('k8s', 'Pod');
2014
}
2115

2216
class(name: string): Class {
@@ -50,19 +44,35 @@ export class LogDomain extends Domain {
5044
queryToLink(query: Query, constraint?: Constraint): URIRef {
5145
const logClass = LogClass[query.class.name as keyof typeof LogClass];
5246
if (!logClass) throw this.badQuery(query, 'unknown class');
53-
try {
54-
// First try to parse the selector as k8s pod selector
55-
const link = this.k8s.queryToLink(this.pod.query(query.selector));
56-
link.pathname = joinPath(link.pathname, 'aggregated-logs');
57-
return link;
58-
} catch {
59-
// Otherwise assume it is a LogQL query.
60-
return new URIRef('monitoring/logs', {
61-
q: query.selector,
62-
tenant: logClass,
63-
start: unixMilliseconds(constraint?.start),
64-
end: unixMilliseconds(constraint?.end),
65-
});
66-
}
47+
return new URIRef('monitoring/logs', {
48+
// Try to translate as a direct pod selector, otherwise use as logQL query
49+
q: directToLogQL(query.selector) || query.selector,
50+
tenant: logClass,
51+
start: unixMilliseconds(constraint?.start),
52+
end: unixMilliseconds(constraint?.end),
53+
});
6754
}
6855
}
56+
57+
const directToLogQL = (maybeDirect: string): string | undefined => {
58+
try {
59+
// Try to parse the selector as k8s pod selector, and translate to logQL.
60+
const direct = JSON.parse(maybeDirect);
61+
if (!direct || typeof direct !== 'object') return undefined;
62+
const streams = [
63+
direct?.namespace && `kubernetes_namespace_name="${direct.namespace}"`,
64+
direct?.name && `kubernetes_pod_name="${direct.name}"`,
65+
]
66+
.filter((x) => x)
67+
.join(',');
68+
const pipeline =
69+
direct?.labels && typeof direct.labels === 'object'
70+
? Object.entries(direct.labels)
71+
.map(([k, v]) => `|kubernetes_labels_${k}="${v}"`)
72+
.join('')
73+
: '';
74+
return `{${streams}}${pipeline ? '|json' + pipeline : ''}`;
75+
} catch {
76+
return undefined;
77+
}
78+
};

0 commit comments

Comments
 (0)