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:
AmberMsy 2020-11-11 14:35:04 +08:00 коммит произвёл GitHub
Родитель df25b98569
Коммит 5fbefb87c6
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
2 изменённых файлов: 114 добавлений и 123 удалений

Просмотреть файл

@ -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`);
}