Skip to content

Commit e896a5c

Browse files
authored
feat(ui): add mustache templating in pod log url (#652)
# Description There are so many cloud providers out there. This PR is used to add templating for pod log urls. Instead of relying only on Stackdriver logs (which is a Google product), we give our users the ability to create their own log urls. There are some variables that can be used by our users. #### Image Builder Log 1. Available Variables - `cluster_name` (string) - `namespace_name` (string) - `job_name` (string) - `start_time` (string) - `end_time` (string) 2. Usage ``` # merlin config.yaml FeatureToggleConfig: LogConfig: LogImageBuilderURL: https://logviewer.sample.local/logs/viewer?cluster={{cluster_name}}&namespace={{namespace_name}}&job={{job_name}} # it generates # https://logviewer.sample.local/logs/viewer?cluster=caraml-cluster&namespace=caraml-namespace&job=job-caraml ``` #### Model Log 1. Available Variables - `cluster_name` (string) - `namespace_name` (string) - `pod_names` (array of {`value`, `is_first`}) - `start_time` (string) 2. Usage ``` # merlin config.yaml FeatureToggleConfig: LogConfig: LogModelURL: https://logviewer.sample.local/logs/viewer?cluster={{cluster_name}}&namespace={{namespace_name}}&pods={{#pod_names}}{{#is_first}}{{value}}{{/is_first}}{{^is_first}},{{value}}{{/is_first}}{{/pod_names}} # it generates # https://logviewer.sample.local/logs/viewer?cluster=caraml-cluster&namespace=caraml-namespace&pods=pod-1,pod-2,pod-3 ``` # Modifications ## BE - add `LogImageBuilderURL` and `LogModelURL` ## FE - add mustache templating - change Stackdriver urls to custom log url with backward compatibility # Tests <!-- Besides the existing / updated automated tests, what specific scenarios should be tested? Consider the backward compatibility of the changes, whether corner cases are covered, etc. Please describe the tests and check the ones that have been completed. Eg: - [x] Deploying new and existing standard models - [ ] Deploying PyFunc models --> # Checklist - [x] Added PR label - [ ] Added unit test, integration, and/or e2e tests - [x] Tested locally - [ ] Updated documentation - [ ] Update Swagger spec if the PR introduce API changes - [ ] Regenerated Golang and Python client if the PR introduces API changes # Release Notes <!-- Does this PR introduce a user-facing change? If no, just write "NONE" in the release-note block below. If yes, a release note is required. Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required". For more information about release notes, see kubernetes' guide here: http://git.k8s.io/community/contributors/guide/release-notes.md --> ```release-note ```
1 parent d366454 commit e896a5c

File tree

6 files changed

+111
-32
lines changed

6 files changed

+111
-32
lines changed

api/cmd/api/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,9 @@ func main() {
171171
MonitoringEnabled: cfg.FeatureToggleConfig.MonitoringConfig.MonitoringEnabled,
172172
MonitoringPredictionJobBaseURL: cfg.FeatureToggleConfig.MonitoringConfig.MonitoringJobBaseURL,
173173

174+
LogImageBuilderURL: cfg.FeatureToggleConfig.LogConfig.LogImageBuilderURL,
175+
LogModelURL: cfg.FeatureToggleConfig.LogConfig.LogModelURL,
176+
174177
ModelDeletionEnabled: cfg.FeatureToggleConfig.ModelDeletionConfig.Enabled,
175178

176179
ImageBuilderCluster: cfg.ImageBuilderConfig.ClusterName,

api/cmd/api/ui_handler.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ type uiEnvHandler struct {
1919
MonitoringEnabled bool `json:"REACT_APP_MONITORING_DASHBOARD_ENABLED"`
2020
MonitoringPredictionJobBaseURL string `json:"REACT_APP_MONITORING_DASHBOARD_JOB_BASE_URL"`
2121

22+
LogImageBuilderURL string `json:"REACT_APP_LOG_IMAGE_BUILDER_URL"`
23+
LogModelURL string `json:"REACT_APP_LOG_MODEL_URL"`
24+
2225
AlertEnabled bool `json:"REACT_APP_ALERT_ENABLED"`
2326

2427
ModelDeletionEnabled bool `json:"REACT_APP_MODEL_DELETION_ENABLED"`

api/config/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ type FeatureToggleConfig struct {
263263
MonitoringConfig MonitoringConfig
264264
AlertConfig AlertConfig
265265
ModelDeletionConfig ModelDeletionConfig
266+
LogConfig LogConfig
266267
}
267268

268269
type MonitoringConfig struct {
@@ -281,6 +282,11 @@ type ModelDeletionConfig struct {
281282
Enabled bool `default:"false"`
282283
}
283284

285+
type LogConfig struct {
286+
LogModelURL string
287+
LogImageBuilderURL string
288+
}
289+
284290
type GitlabConfig struct {
285291
BaseURL string
286292
Token string

ui/src/components/logs/ContainerLogsView.js

Lines changed: 62 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ import {
1313
} from "@elastic/eui";
1414
import React, { Fragment, useContext, useEffect, useState } from "react";
1515
import { LazyLog, ScrollFollow } from "react-lazylog";
16-
import config from "../../config";
16+
import config, {appConfig, featureToggleConfig} from "../../config";
1717
import { useMerlinApi } from "../../hooks/useMerlinApi";
1818
import mocks from "../../mocks";
19-
import { createStackdriverUrl } from "../../utils/createStackdriverUrl";
2019
import { LogsSearchBar } from "./LogsSearchBar";
20+
import {createLogImageBuilderUrl, createLogModelUrl} from "../../utils/createLogUrl";
21+
import {createStackdriverUrl} from "../../utils/createStackdriverUrl";
2122

2223
const componentOrder = [
2324
"image_builder",
@@ -121,22 +122,36 @@ export const ContainerLogsView = ({
121122
};
122123

123124
const [logUrl, setLogUrl] = useState("");
124-
const [stackdriverUrls, setStackdriverUrls] = useState({});
125+
const [podLogUrls, setPodLogUrls] = useState({});
125126
useEffect(
126127
() => {
127128
if (projectLoaded) {
128129
// set image builder url
129-
let stackdriverQuery = {
130-
job_name: project.name + "-" + model.name + "-" + versionId,
131-
start_time: model.updated_at,
132-
};
133-
setStackdriverUrls({
134-
...stackdriverUrls,
135-
image_builder: createStackdriverUrl(
136-
stackdriverQuery,
137-
"image_builder",
138-
),
139-
});
130+
if (featureToggleConfig.logImageBuilderURL) {
131+
setPodLogUrls({
132+
...podLogUrls,
133+
image_builder: createLogImageBuilderUrl(
134+
featureToggleConfig.logImageBuilderURL,
135+
appConfig.imagebuilder.cluster,
136+
appConfig.imagebuilder.namespace,
137+
project.name + "-" + model.name + "-" + versionId,
138+
model.updated_at,
139+
),
140+
});
141+
} else {
142+
// backward compatible to stackdriver
143+
let stackdriverQuery = {
144+
job_name: project.name + "-" + model.name + "-" + versionId,
145+
start_time: model.updated_at,
146+
};
147+
setPodLogUrls({
148+
...podLogUrls,
149+
image_builder: createStackdriverUrl(
150+
stackdriverQuery,
151+
"image_builder",
152+
),
153+
});
154+
}
140155

141156
// update active container
142157
if (params.component_type !== "") {
@@ -165,24 +180,39 @@ export const ContainerLogsView = ({
165180

166181
const pods = [
167182
...new Set(
168-
activeContainers.map((container) => `"${container.pod_name}"`),
183+
activeContainers.map((container) => `${container.pod_name}`),
169184
),
170185
];
171-
let stackdriverQuery = {
172-
gcp_project: activeContainers[0].gcp_project,
173-
cluster: activeContainers[0].cluster,
174-
namespace: activeContainers[0].namespace,
175-
pod_name: pods.join(" OR "),
176-
start_time: model.updated_at,
177-
};
186+
178187
if (params.component_type !== "image_builder") {
179-
setStackdriverUrls({
180-
...stackdriverUrls,
181-
[params.component_type]: createStackdriverUrl(
182-
stackdriverQuery,
183-
params.component_type,
184-
),
185-
});
188+
if (featureToggleConfig.logModelURL) {
189+
setPodLogUrls({
190+
...podLogUrls,
191+
[params.component_type]: createLogModelUrl(
192+
featureToggleConfig.logModelURL,
193+
activeContainers[0].cluster,
194+
activeContainers[0].namespace,
195+
pods,
196+
model.updated_at,
197+
),
198+
});
199+
} else {
200+
// backward compatible to stackdriver
201+
let stackdriverQuery = {
202+
gcp_project: activeContainers[0].gcp_project,
203+
cluster: activeContainers[0].cluster,
204+
namespace: activeContainers[0].namespace,
205+
pod_name: pods.map(pod => `"${pod}"`).join(" OR "),
206+
start_time: model.updated_at,
207+
};
208+
setPodLogUrls({
209+
...podLogUrls,
210+
[params.component_type]: createStackdriverUrl(
211+
stackdriverQuery,
212+
params.component_type,
213+
),
214+
});
215+
}
186216
}
187217
}
188218
}
@@ -199,7 +229,7 @@ export const ContainerLogsView = ({
199229
<EuiTextColor color="success">&nbsp; Logs</EuiTextColor>
200230
</span>
201231
</EuiTitle>
202-
{Object.keys(stackdriverUrls).length !== 0 && (
232+
{Object.keys(podLogUrls).length !== 0 && (
203233
<Fragment>
204234
<EuiSpacer size="s" />
205235
<EuiPanel>
@@ -209,10 +239,10 @@ export const ContainerLogsView = ({
209239
grow={false}
210240
>
211241
<EuiText style={{ fontSize: "14px", fontWeight: "bold" }}>
212-
Stackdriver Logs
242+
Pod Logs
213243
</EuiText>
214244
</EuiFlexItem>
215-
{Object.entries(stackdriverUrls).map(([component, url]) => (
245+
{Object.entries(podLogUrls).map(([component, url]) => (
216246
<EuiFlexItem
217247
style={{
218248
marginTop: 0,

ui/src/config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ export const featureToggleConfig = {
8585
"false"
8686
)
8787
: false,
88+
logImageBuilderURL: getEnv(
89+
"REACT_APP_LOG_IMAGE_BUILDER_URL"
90+
),
91+
logModelURL: getEnv(
92+
"REACT_APP_LOG_MODEL_URL"
93+
),
8894
};
8995

9096
export const costEstimationConfig = {

ui/src/utils/createLogUrl.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Mustache from "mustache";
2+
const moment = require("moment");
3+
4+
export const createLogImageBuilderUrl = (template, cluster, namespace, jobName, startTime) => {
5+
const endTime = moment(startTime, "YYYY-MM-DDTHH:mm.SSZ").add(1, "hour") // we assume the image builder finished after 1 hour
6+
const data = {
7+
cluster_name: cluster,
8+
namespace_name: namespace,
9+
job_name: jobName,
10+
start_time: startTime,
11+
end_time: endTime.toISOString(),
12+
};
13+
14+
return Mustache.render(template, data);
15+
}
16+
17+
export const createLogModelUrl = (template, cluster, namespace, podNames, startTime) => {
18+
const data = {
19+
cluster_name: cluster,
20+
namespace_name: namespace,
21+
pod_names: podNames.map((val, idx) => (
22+
{
23+
value: val,
24+
is_first: idx === 0,
25+
}
26+
)),
27+
start_time: startTime,
28+
};
29+
30+
return Mustache.render(template, data);
31+
}

0 commit comments

Comments
 (0)