зеркало из 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 Timer from './timer';
|
||||
import { getContainerLog } from '../conn';
|
||||
import { getContainerLog, getContainerLogList } from '../conn';
|
||||
import config from '../../../../../config/webportal.config';
|
||||
import MonacoPanel from '../../../../../components/monaco-panel';
|
||||
import StatusBadge from '../../../../../components/status-badge';
|
||||
|
@ -137,7 +137,10 @@ export default class TaskRoleContainerList extends React.Component {
|
|||
monacoProps: null,
|
||||
monacoTitle: '',
|
||||
monacoFooterButton: null,
|
||||
logUrl: null,
|
||||
fullLogUrls: null,
|
||||
tailLogUrls: null,
|
||||
logListUrl: null,
|
||||
logType: null,
|
||||
items: props.tasks,
|
||||
ordering: { field: null, descending: false },
|
||||
hideDialog: true,
|
||||
|
@ -145,7 +148,7 @@ export default class TaskRoleContainerList extends React.Component {
|
|||
|
||||
this.showSshInfo = this.showSshInfo.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.logAutoRefresh = this.logAutoRefresh.bind(this);
|
||||
this.onColumnClick = this.onColumnClick.bind(this);
|
||||
|
@ -159,12 +162,12 @@ export default class TaskRoleContainerList extends React.Component {
|
|||
}
|
||||
|
||||
logAutoRefresh() {
|
||||
const { logUrl } = this.state;
|
||||
getContainerLog(logUrl)
|
||||
const { fullLogUrls, tailLogUrls, logListUrl, logType } = this.state;
|
||||
getContainerLog(tailLogUrls, fullLogUrls, logType)
|
||||
.then(({ text, fullLogLink }) =>
|
||||
this.setState(
|
||||
prevState =>
|
||||
prevState.logUrl === logUrl && {
|
||||
prevState.tailLogUrls[logType] === tailLogUrls[logType] && {
|
||||
monacoProps: { value: text },
|
||||
monacoFooterButton: (
|
||||
<PrimaryButton
|
||||
|
@ -179,14 +182,17 @@ export default class TaskRoleContainerList extends React.Component {
|
|||
},
|
||||
),
|
||||
)
|
||||
.catch(err =>
|
||||
.catch(err => {
|
||||
this.setState(
|
||||
prevState =>
|
||||
prevState.logUrl === logUrl && {
|
||||
prevState.tailLogUrls[logType] === tailLogUrls[logType] && {
|
||||
monacoProps: { value: err.message },
|
||||
},
|
||||
),
|
||||
);
|
||||
);
|
||||
if (err.message === '403') {
|
||||
this.showContainerTailLog(logListUrl, logType);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onDismiss() {
|
||||
|
@ -194,7 +200,8 @@ export default class TaskRoleContainerList extends React.Component {
|
|||
monacoProps: null,
|
||||
monacoTitle: '',
|
||||
monacoFooterButton: null,
|
||||
logUrl: null,
|
||||
fullLogUrls: null,
|
||||
tailLogUrls: null,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -213,40 +220,52 @@ export default class TaskRoleContainerList extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
showContainerLog(logUrl, logType) {
|
||||
let title;
|
||||
let logHint;
|
||||
convertObjectFormat(logUrls) {
|
||||
const logs = {};
|
||||
for (const p in logUrls.locations) {
|
||||
logs[logUrls.locations[p].name] = logUrls.locations[p].uri;
|
||||
}
|
||||
return logs;
|
||||
}
|
||||
|
||||
if (config.logType === 'yarn') {
|
||||
logHint = 'Last 4096 bytes';
|
||||
} else if (config.logType === 'log-manager') {
|
||||
logHint = 'Last 16384 bytes';
|
||||
} else {
|
||||
logHint = '';
|
||||
}
|
||||
switch (logType) {
|
||||
case 'stdout':
|
||||
title = `Standard Output (${logHint})`;
|
||||
break;
|
||||
case 'stderr':
|
||||
title = `Standard Error (${logHint})`;
|
||||
break;
|
||||
case 'stdall':
|
||||
title = `User logs (${logHint}. Notice: The logs may out of order when merging stdout & stderr streams)`;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported log type`);
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
monacoProps: { value: 'Loading...' },
|
||||
monacoTitle: title,
|
||||
logUrl,
|
||||
},
|
||||
() => {
|
||||
this.logAutoRefresh(); // start immediately
|
||||
},
|
||||
);
|
||||
showContainerTailLog(logListUrl, logType) {
|
||||
let title;
|
||||
let logHint = '';
|
||||
this.setState({ logListUrl: logListUrl });
|
||||
getContainerLogList(logListUrl)
|
||||
.then(({ fullLogUrls, tailLogUrls }) => {
|
||||
if (config.logType === 'log-manager') {
|
||||
logHint = 'Last 16384 bytes';
|
||||
}
|
||||
switch (logType) {
|
||||
case 'stdout':
|
||||
title = `Standard Output (${logHint})`;
|
||||
break;
|
||||
case 'stderr':
|
||||
title = `Standard Error (${logHint})`;
|
||||
break;
|
||||
case 'all':
|
||||
title = `User logs (${logHint}. Notice: The logs may out of order when merging stdout & stderr streams)`;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported log type`);
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
monacoProps: { value: 'Loading...' },
|
||||
monacoTitle: title,
|
||||
fullLogUrls: this.convertObjectFormat(fullLogUrls),
|
||||
tailLogUrls: this.convertObjectFormat(tailLogUrls),
|
||||
logType,
|
||||
},
|
||||
() => {
|
||||
this.logAutoRefresh(); // start immediately
|
||||
},
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({ monacoProps: { value: err.message } });
|
||||
});
|
||||
}
|
||||
|
||||
showSshInfo(id, containerPorts, containerIp) {
|
||||
|
@ -424,7 +443,7 @@ export default class TaskRoleContainerList extends React.Component {
|
|||
monacoTitle,
|
||||
monacoProps,
|
||||
monacoFooterButton,
|
||||
logUrl,
|
||||
tailLogUrls,
|
||||
items,
|
||||
} = this.state;
|
||||
const { showMoreDiagnostics } = this.props;
|
||||
|
@ -443,7 +462,9 @@ export default class TaskRoleContainerList extends React.Component {
|
|||
</ThemeProvider>
|
||||
{/* Timer */}
|
||||
<Timer
|
||||
interval={isNil(monacoProps) || isEmpty(logUrl) ? null : interval}
|
||||
interval={
|
||||
isNil(monacoProps) || isEmpty(tailLogUrls) ? null : interval
|
||||
}
|
||||
func={this.logAutoRefresh}
|
||||
/>
|
||||
{/* Monaco Editor Panel */}
|
||||
|
@ -624,8 +645,8 @@ export default class TaskRoleContainerList extends React.Component {
|
|||
iconProps={{ iconName: 'TextDocument' }}
|
||||
text='Stdout'
|
||||
onClick={() =>
|
||||
this.showContainerLog(
|
||||
`${item.containerLog}user.pai.stdout`,
|
||||
this.showContainerTailLog(
|
||||
`${config.restServerUri}${item.containerLog}`,
|
||||
'stdout',
|
||||
)
|
||||
}
|
||||
|
@ -640,8 +661,8 @@ export default class TaskRoleContainerList extends React.Component {
|
|||
iconProps={{ iconName: 'Error' }}
|
||||
text='Stderr'
|
||||
onClick={() =>
|
||||
this.showContainerLog(
|
||||
`${item.containerLog}user.pai.stderr`,
|
||||
this.showContainerTailLog(
|
||||
`${config.restServerUri}${item.containerLog}`,
|
||||
'stderr',
|
||||
)
|
||||
}
|
||||
|
@ -662,23 +683,11 @@ export default class TaskRoleContainerList extends React.Component {
|
|||
iconProps: { iconName: 'TextDocument' },
|
||||
disabled: isNil(item.containerId),
|
||||
onClick: () =>
|
||||
this.showContainerLog(
|
||||
`${item.containerLog}user.pai.all`,
|
||||
'stdall',
|
||||
this.showContainerTailLog(
|
||||
`${config.restServerUri}${item.containerLog}`,
|
||||
'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)}
|
||||
|
|
|
@ -12,7 +12,6 @@ import config from '../../../../config/webportal.config';
|
|||
const params = new URLSearchParams(window.location.search);
|
||||
const userName = params.get('username');
|
||||
const jobName = params.get('jobName');
|
||||
const absoluteUrlRegExp = /^[a-z][a-z\d+.-]*:/;
|
||||
const token = cookies.get('token');
|
||||
|
||||
const client = new PAIV2.OpenPAIClient({
|
||||
|
@ -157,77 +156,60 @@ export async function stopJob() {
|
|||
);
|
||||
}
|
||||
|
||||
export async function getContainerLog(logUrl) {
|
||||
const ret = {
|
||||
fullLogLink: logUrl,
|
||||
text: null,
|
||||
export async function getContainerLogList(logListUrl) {
|
||||
const res = await Promise.all([
|
||||
fetch(`${logListUrl}`, {
|
||||
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) {
|
||||
throw new Error(res.statusText);
|
||||
throw new Error(res.status);
|
||||
}
|
||||
let text = await res.text();
|
||||
|
||||
const contentType = res.headers.get('content-type');
|
||||
if (!contentType) {
|
||||
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') {
|
||||
// Check log type. The log type is in LOG_TYPE only support log-manager.
|
||||
if (config.logType === 'log-manager') {
|
||||
// Try to get roated log if currently log content is less than 15KB
|
||||
if (text.length <= 15 * 1024) {
|
||||
const fullLogUrl = logUrl.replace('/tail/', '/full/');
|
||||
const rotatedLogUrl = logUrl + '.1';
|
||||
if (text.length <= 15 * 1024 && tailLogUrls[logType + '.1']) {
|
||||
const rotatedLogUrl = tailLogUrls[logType + '.1'];
|
||||
const rotatedLogRes = await fetch(rotatedLogUrl);
|
||||
const fullLogRes = await fetch(fullLogUrl);
|
||||
const fullLogRes = await fetch(fullLogUrls[logType]);
|
||||
const rotatedText = await rotatedLogRes.text();
|
||||
const fullLog = await fullLogRes.text();
|
||||
if (rotatedLogRes.ok && rotatedText.trim() !== 'No such file!') {
|
||||
if (rotatedLogRes.ok) {
|
||||
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);
|
||||
}
|
||||
// get last 16KB
|
||||
text = text.slice(-16 * 1024);
|
||||
}
|
||||
ret.text = text;
|
||||
ret.fullLogLink = logUrl.replace('/tail/', '/full/');
|
||||
return ret;
|
||||
return {
|
||||
fullLogLink: fullLogUrls[logType],
|
||||
text: text,
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Log not available`);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче