зеркало из https://github.com/microsoft/pai.git
Update Stdout/Stderr/Stdout+Stderr button (#5063)
* log web * change * Get Log * Url Token Refresh * merge log&log.1 * fix merge part * fix refresh part * fix by prettier * fix state Co-authored-by: Binyang Li <binyli@microsoft.com>
This commit is contained in:
Родитель
df25b98569
Коммит
5fbefb87c6
|
@ -48,7 +48,7 @@ import t from '../../../../../components/tachyons.scss';
|
||||||
|
|
||||||
import Context from './context';
|
import Context from './context';
|
||||||
import Timer from './timer';
|
import Timer from './timer';
|
||||||
import { getContainerLog } from '../conn';
|
import { getContainerLog, getContainerLogList } from '../conn';
|
||||||
import config from '../../../../../config/webportal.config';
|
import config from '../../../../../config/webportal.config';
|
||||||
import MonacoPanel from '../../../../../components/monaco-panel';
|
import MonacoPanel from '../../../../../components/monaco-panel';
|
||||||
import StatusBadge from '../../../../../components/status-badge';
|
import StatusBadge from '../../../../../components/status-badge';
|
||||||
|
@ -137,7 +137,10 @@ export default class TaskRoleContainerList extends React.Component {
|
||||||
monacoProps: null,
|
monacoProps: null,
|
||||||
monacoTitle: '',
|
monacoTitle: '',
|
||||||
monacoFooterButton: null,
|
monacoFooterButton: null,
|
||||||
logUrl: null,
|
fullLogUrls: null,
|
||||||
|
tailLogUrls: null,
|
||||||
|
logListUrl: null,
|
||||||
|
logType: null,
|
||||||
items: props.tasks,
|
items: props.tasks,
|
||||||
ordering: { field: null, descending: false },
|
ordering: { field: null, descending: false },
|
||||||
hideDialog: true,
|
hideDialog: true,
|
||||||
|
@ -145,7 +148,7 @@ export default class TaskRoleContainerList extends React.Component {
|
||||||
|
|
||||||
this.showSshInfo = this.showSshInfo.bind(this);
|
this.showSshInfo = this.showSshInfo.bind(this);
|
||||||
this.onDismiss = this.onDismiss.bind(this);
|
this.onDismiss = this.onDismiss.bind(this);
|
||||||
this.showContainerLog = this.showContainerLog.bind(this);
|
this.showContainerTailLog = this.showContainerTailLog.bind(this);
|
||||||
this.onRenderRow = this.onRenderRow.bind(this);
|
this.onRenderRow = this.onRenderRow.bind(this);
|
||||||
this.logAutoRefresh = this.logAutoRefresh.bind(this);
|
this.logAutoRefresh = this.logAutoRefresh.bind(this);
|
||||||
this.onColumnClick = this.onColumnClick.bind(this);
|
this.onColumnClick = this.onColumnClick.bind(this);
|
||||||
|
@ -159,12 +162,12 @@ export default class TaskRoleContainerList extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
logAutoRefresh() {
|
logAutoRefresh() {
|
||||||
const { logUrl } = this.state;
|
const { fullLogUrls, tailLogUrls, logListUrl, logType } = this.state;
|
||||||
getContainerLog(logUrl)
|
getContainerLog(tailLogUrls, fullLogUrls, logType)
|
||||||
.then(({ text, fullLogLink }) =>
|
.then(({ text, fullLogLink }) =>
|
||||||
this.setState(
|
this.setState(
|
||||||
prevState =>
|
prevState =>
|
||||||
prevState.logUrl === logUrl && {
|
prevState.tailLogUrls[logType] === tailLogUrls[logType] && {
|
||||||
monacoProps: { value: text },
|
monacoProps: { value: text },
|
||||||
monacoFooterButton: (
|
monacoFooterButton: (
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
|
@ -179,14 +182,17 @@ export default class TaskRoleContainerList extends React.Component {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.catch(err =>
|
.catch(err => {
|
||||||
this.setState(
|
this.setState(
|
||||||
prevState =>
|
prevState =>
|
||||||
prevState.logUrl === logUrl && {
|
prevState.tailLogUrls[logType] === tailLogUrls[logType] && {
|
||||||
monacoProps: { value: err.message },
|
monacoProps: { value: err.message },
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
);
|
if (err.message === '403') {
|
||||||
|
this.showContainerTailLog(logListUrl, logType);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onDismiss() {
|
onDismiss() {
|
||||||
|
@ -194,7 +200,8 @@ export default class TaskRoleContainerList extends React.Component {
|
||||||
monacoProps: null,
|
monacoProps: null,
|
||||||
monacoTitle: '',
|
monacoTitle: '',
|
||||||
monacoFooterButton: null,
|
monacoFooterButton: null,
|
||||||
logUrl: null,
|
fullLogUrls: null,
|
||||||
|
tailLogUrls: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,40 +220,52 @@ export default class TaskRoleContainerList extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showContainerLog(logUrl, logType) {
|
convertObjectFormat(logUrls) {
|
||||||
let title;
|
const logs = {};
|
||||||
let logHint;
|
for (const p in logUrls.locations) {
|
||||||
|
logs[logUrls.locations[p].name] = logUrls.locations[p].uri;
|
||||||
|
}
|
||||||
|
return logs;
|
||||||
|
}
|
||||||
|
|
||||||
if (config.logType === 'yarn') {
|
showContainerTailLog(logListUrl, logType) {
|
||||||
logHint = 'Last 4096 bytes';
|
let title;
|
||||||
} else if (config.logType === 'log-manager') {
|
let logHint = '';
|
||||||
logHint = 'Last 16384 bytes';
|
this.setState({ logListUrl: logListUrl });
|
||||||
} else {
|
getContainerLogList(logListUrl)
|
||||||
logHint = '';
|
.then(({ fullLogUrls, tailLogUrls }) => {
|
||||||
}
|
if (config.logType === 'log-manager') {
|
||||||
switch (logType) {
|
logHint = 'Last 16384 bytes';
|
||||||
case 'stdout':
|
}
|
||||||
title = `Standard Output (${logHint})`;
|
switch (logType) {
|
||||||
break;
|
case 'stdout':
|
||||||
case 'stderr':
|
title = `Standard Output (${logHint})`;
|
||||||
title = `Standard Error (${logHint})`;
|
break;
|
||||||
break;
|
case 'stderr':
|
||||||
case 'stdall':
|
title = `Standard Error (${logHint})`;
|
||||||
title = `User logs (${logHint}. Notice: The logs may out of order when merging stdout & stderr streams)`;
|
break;
|
||||||
break;
|
case 'all':
|
||||||
default:
|
title = `User logs (${logHint}. Notice: The logs may out of order when merging stdout & stderr streams)`;
|
||||||
throw new Error(`Unsupported log type`);
|
break;
|
||||||
}
|
default:
|
||||||
this.setState(
|
throw new Error(`Unsupported log type`);
|
||||||
{
|
}
|
||||||
monacoProps: { value: 'Loading...' },
|
this.setState(
|
||||||
monacoTitle: title,
|
{
|
||||||
logUrl,
|
monacoProps: { value: 'Loading...' },
|
||||||
},
|
monacoTitle: title,
|
||||||
() => {
|
fullLogUrls: this.convertObjectFormat(fullLogUrls),
|
||||||
this.logAutoRefresh(); // start immediately
|
tailLogUrls: this.convertObjectFormat(tailLogUrls),
|
||||||
},
|
logType,
|
||||||
);
|
},
|
||||||
|
() => {
|
||||||
|
this.logAutoRefresh(); // start immediately
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.setState({ monacoProps: { value: err.message } });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showSshInfo(id, containerPorts, containerIp) {
|
showSshInfo(id, containerPorts, containerIp) {
|
||||||
|
@ -424,7 +443,7 @@ export default class TaskRoleContainerList extends React.Component {
|
||||||
monacoTitle,
|
monacoTitle,
|
||||||
monacoProps,
|
monacoProps,
|
||||||
monacoFooterButton,
|
monacoFooterButton,
|
||||||
logUrl,
|
tailLogUrls,
|
||||||
items,
|
items,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { showMoreDiagnostics } = this.props;
|
const { showMoreDiagnostics } = this.props;
|
||||||
|
@ -443,7 +462,9 @@ export default class TaskRoleContainerList extends React.Component {
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
{/* Timer */}
|
{/* Timer */}
|
||||||
<Timer
|
<Timer
|
||||||
interval={isNil(monacoProps) || isEmpty(logUrl) ? null : interval}
|
interval={
|
||||||
|
isNil(monacoProps) || isEmpty(tailLogUrls) ? null : interval
|
||||||
|
}
|
||||||
func={this.logAutoRefresh}
|
func={this.logAutoRefresh}
|
||||||
/>
|
/>
|
||||||
{/* Monaco Editor Panel */}
|
{/* Monaco Editor Panel */}
|
||||||
|
@ -624,8 +645,8 @@ export default class TaskRoleContainerList extends React.Component {
|
||||||
iconProps={{ iconName: 'TextDocument' }}
|
iconProps={{ iconName: 'TextDocument' }}
|
||||||
text='Stdout'
|
text='Stdout'
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
this.showContainerLog(
|
this.showContainerTailLog(
|
||||||
`${item.containerLog}user.pai.stdout`,
|
`${config.restServerUri}${item.containerLog}`,
|
||||||
'stdout',
|
'stdout',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -640,8 +661,8 @@ export default class TaskRoleContainerList extends React.Component {
|
||||||
iconProps={{ iconName: 'Error' }}
|
iconProps={{ iconName: 'Error' }}
|
||||||
text='Stderr'
|
text='Stderr'
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
this.showContainerLog(
|
this.showContainerTailLog(
|
||||||
`${item.containerLog}user.pai.stderr`,
|
`${config.restServerUri}${item.containerLog}`,
|
||||||
'stderr',
|
'stderr',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -662,23 +683,11 @@ export default class TaskRoleContainerList extends React.Component {
|
||||||
iconProps: { iconName: 'TextDocument' },
|
iconProps: { iconName: 'TextDocument' },
|
||||||
disabled: isNil(item.containerId),
|
disabled: isNil(item.containerId),
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
this.showContainerLog(
|
this.showContainerTailLog(
|
||||||
`${item.containerLog}user.pai.all`,
|
`${config.restServerUri}${item.containerLog}`,
|
||||||
'stdall',
|
'all',
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'trackingPage',
|
|
||||||
name:
|
|
||||||
config.launcherType === 'yarn'
|
|
||||||
? 'Go to Yarn Tracking Page'
|
|
||||||
: 'Browse log folder',
|
|
||||||
iconProps: { iconName: 'Link' },
|
|
||||||
href: isNil(item.containerLog)
|
|
||||||
? item.containerLog
|
|
||||||
: item.containerLog.replace('/tail/', '/'),
|
|
||||||
target: '_blank',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
disabled={isNil(item.containerId)}
|
disabled={isNil(item.containerId)}
|
||||||
|
|
|
@ -12,7 +12,6 @@ import config from '../../../../config/webportal.config';
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const userName = params.get('username');
|
const userName = params.get('username');
|
||||||
const jobName = params.get('jobName');
|
const jobName = params.get('jobName');
|
||||||
const absoluteUrlRegExp = /^[a-z][a-z\d+.-]*:/;
|
|
||||||
const token = cookies.get('token');
|
const token = cookies.get('token');
|
||||||
|
|
||||||
const client = new PAIV2.OpenPAIClient({
|
const client = new PAIV2.OpenPAIClient({
|
||||||
|
@ -157,77 +156,60 @@ export async function stopJob() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getContainerLog(logUrl) {
|
export async function getContainerLogList(logListUrl) {
|
||||||
const ret = {
|
const res = await Promise.all([
|
||||||
fullLogLink: logUrl,
|
fetch(`${logListUrl}`, {
|
||||||
text: null,
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
fetch(`${logListUrl}?tail-mode=true`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
const resp = res.find(r => !r.ok);
|
||||||
|
if (resp) {
|
||||||
|
throw new Error('Log folder can not be retrieved');
|
||||||
|
}
|
||||||
|
const logUrls = await Promise.all(res.map(r => r.json()));
|
||||||
|
return {
|
||||||
|
fullLogUrls: logUrls[0],
|
||||||
|
tailLogUrls: logUrls[1],
|
||||||
};
|
};
|
||||||
const res = await fetch(logUrl);
|
}
|
||||||
var text = await res.text();
|
|
||||||
|
export async function getContainerLog(tailLogUrls, fullLogUrls, logType) {
|
||||||
|
const res = await fetch(tailLogUrls[logType]);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error(res.statusText);
|
throw new Error(res.status);
|
||||||
}
|
}
|
||||||
|
let text = await res.text();
|
||||||
|
|
||||||
const contentType = res.headers.get('content-type');
|
// Check log type. The log type is in LOG_TYPE only support log-manager.
|
||||||
if (!contentType) {
|
if (config.logType === 'log-manager') {
|
||||||
throw new Error(`Log not available`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check log type. The log type is in LOG_TYPE and should be yarn|log-manager.
|
|
||||||
if (config.logType === 'yarn') {
|
|
||||||
try {
|
|
||||||
const parser = new DOMParser();
|
|
||||||
const doc = parser.parseFromString(text, 'text/html');
|
|
||||||
const content = doc.getElementsByClassName('content')[0];
|
|
||||||
const pre = content.getElementsByTagName('pre')[0];
|
|
||||||
ret.text = pre.innerText;
|
|
||||||
// fetch full log link
|
|
||||||
if (pre.previousElementSibling) {
|
|
||||||
const link = pre.previousElementSibling.getElementsByTagName('a');
|
|
||||||
if (link.length === 1) {
|
|
||||||
ret.fullLogLink = link[0].getAttribute('href');
|
|
||||||
// relative link
|
|
||||||
if (ret.fullLogLink && !absoluteUrlRegExp.test(ret.fullLogLink)) {
|
|
||||||
let baseUrl = res.url;
|
|
||||||
// check base tag
|
|
||||||
const baseTags = doc.getElementsByTagName('base');
|
|
||||||
// There can be only one <base> element in a document.
|
|
||||||
if (baseTags.length > 0 && baseTags[0].hasAttribute('href')) {
|
|
||||||
baseUrl = baseTags[0].getAttribute('href');
|
|
||||||
// relative base tag url
|
|
||||||
if (!absoluteUrlRegExp.test(baseUrl)) {
|
|
||||||
baseUrl = new URL(baseUrl, res.url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const url = new URL(ret.fullLogLink, baseUrl);
|
|
||||||
ret.fullLogLink = url.href;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(`Log not available`);
|
|
||||||
}
|
|
||||||
} else if (config.logType === 'log-manager') {
|
|
||||||
// Try to get roated log if currently log content is less than 15KB
|
// Try to get roated log if currently log content is less than 15KB
|
||||||
if (text.length <= 15 * 1024) {
|
if (text.length <= 15 * 1024 && tailLogUrls[logType + '.1']) {
|
||||||
const fullLogUrl = logUrl.replace('/tail/', '/full/');
|
const rotatedLogUrl = tailLogUrls[logType + '.1'];
|
||||||
const rotatedLogUrl = logUrl + '.1';
|
|
||||||
const rotatedLogRes = await fetch(rotatedLogUrl);
|
const rotatedLogRes = await fetch(rotatedLogUrl);
|
||||||
const fullLogRes = await fetch(fullLogUrl);
|
const fullLogRes = await fetch(fullLogUrls[logType]);
|
||||||
const rotatedText = await rotatedLogRes.text();
|
const rotatedText = await rotatedLogRes.text();
|
||||||
const fullLog = await fullLogRes.text();
|
const fullLog = await fullLogRes.text();
|
||||||
if (rotatedLogRes.ok && rotatedText.trim() !== 'No such file!') {
|
if (rotatedLogRes.ok) {
|
||||||
text = rotatedText
|
text = rotatedText
|
||||||
.concat('\n--------log is rotated, may be lost during this--------\n')
|
.concat(
|
||||||
|
'\n ------- log is rotated, may be lost during this ------- \n',
|
||||||
|
)
|
||||||
.concat(fullLog);
|
.concat(fullLog);
|
||||||
}
|
}
|
||||||
// get last 16KB
|
// get last 16KB
|
||||||
text = text.slice(-16 * 1024);
|
text = text.slice(-16 * 1024);
|
||||||
}
|
}
|
||||||
ret.text = text;
|
return {
|
||||||
ret.fullLogLink = logUrl.replace('/tail/', '/full/');
|
fullLogLink: fullLogUrls[logType],
|
||||||
return ret;
|
text: text,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Log not available`);
|
throw new Error(`Log not available`);
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче