Skip to content

Commit

Permalink
Ping Function App so that logging works immediately (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
ejizba authored Feb 16, 2018
1 parent 15ce982 commit 0f4661b
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 13 deletions.
2 changes: 1 addition & 1 deletion appservice/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "vscode-azureappservice",
"author": "Microsoft Corporation",
"version": "0.8.4",
"version": "0.8.5",
"description": "Common tools for developing Azure App Service extensions for VS Code",
"tags": [
"azure",
Expand Down
52 changes: 40 additions & 12 deletions appservice/src/SiteWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
import WebSiteManagementClient = require('azure-arm-website');
import { AppServicePlan, Site, SiteConfigResource, SiteLogsConfig, User } from 'azure-arm-website/lib/models';
import * as fs from 'fs';
import { BasicAuthenticationCredentials, WebResource } from 'ms-rest';
import { BasicAuthenticationCredentials, ServiceClientCredentials, TokenCredentials, WebResource } from 'ms-rest';
import * as opn from 'opn';
import * as request from 'request';
import * as git from 'simple-git/promise';
import { setInterval } from 'timers';
import * as vscode from 'vscode';
import { AzureActionHandler, parseError, UserCancelledError } from 'vscode-azureextensionui';
import KuduClient from 'vscode-azurekudu';
Expand All @@ -34,11 +35,13 @@ export class SiteWrapper {
public readonly planResourceGroup: string;
public readonly planName: string;
public readonly id: string;
public readonly defaultHostName: string;
public readonly isFunctionApp: boolean;
private readonly _gitUrl: string;

constructor(site: Site) {
const matches: RegExpMatchArray | null = site.serverFarmId.match(/\/subscriptions\/(.*)\/resourceGroups\/(.*)\/providers\/Microsoft.Web\/serverfarms\/(.*)/);
if (!site.id || !site.name || !site.resourceGroup || !site.type || matches === null || matches.length < 4) {
if (!site.id || !site.name || !site.resourceGroup || !site.type || !site.defaultHostName || matches === null || matches.length < 4) {
throw new ArgumentError(site);
}

Expand All @@ -50,6 +53,8 @@ export class SiteWrapper {
this.slotName = isSlot ? site.name.substring(site.name.lastIndexOf('/') + 1) : undefined;
// the scm url used for git repo is in index 1 of enabledHostNames, not 0
this._gitUrl = `${site.enabledHostNames[1]}:443/${site.repositorySiteName}.git`;
this.defaultHostName = site.defaultHostName;
this.isFunctionApp = site.kind === 'functionapp';

this.planResourceGroup = matches[2];
this.planName = matches[3];
Expand Down Expand Up @@ -212,34 +217,38 @@ export class SiteWrapper {
* Starts the log-streaming service. Call 'dispose()' on the returned object when you want to stop the service.
*/
public async startStreamingLogs(client: KuduClient, actionHandler: AzureActionHandler, outputChannel: vscode.OutputChannel, path: string = ''): Promise<ILogStream> {
outputChannel.show();
outputChannel.appendLine(localize('connectingToLogStream', 'Connecting to log stream...'));
const httpRequest: WebResource = new WebResource();
await new Promise((resolve: () => void, reject: (err: Error) => void): void => {
client.credentials.signRequest(httpRequest, (err: Error) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
await signRequest(httpRequest, client.credentials);

const requestApi: request.RequestAPI<request.Request, request.CoreOptions, {}> = request.defaults(httpRequest);
const logStream: ILogStream = { dispose: undefined, isConnected: true };
// Intentionally setting up a separate telemetry event and not awaiting the result here since log stream is a long-running action
// tslint:disable-next-line:no-floating-promises
actionHandler.callWithTelemetry('appService.streamingLogs', async () => {
let timerId: NodeJS.Timer | undefined;
if (this.isFunctionApp) {
// For Function Apps, we have to ping "/admin/host/status" every minute for logging to work
// https://github.com/Microsoft/vscode-azurefunctions/issues/227
await this.pingFunctionApp(client);
timerId = setInterval(async () => await this.pingFunctionApp(client), 60 * 1000);
}

await new Promise((resolve: () => void, reject: (err: Error) => void): void => {
const logsRequest: request.Request = requestApi(`${this.kuduUrl}/api/logstream/${path}`);
logStream.dispose = (): void => {
logsRequest.removeAllListeners();
logsRequest.destroy();
outputChannel.show();
if (timerId) {
clearInterval(timerId);
}
outputChannel.appendLine(localize('logStreamDisconnected', 'Disconnected from log-streaming service.'));
logStream.isConnected = false;
resolve();
};

outputChannel.show();
logsRequest.on('data', (chunk: Buffer | string) => {
outputChannel.appendLine(chunk.toString());
}).on('error', (err: Error) => {
Expand All @@ -255,6 +264,13 @@ export class SiteWrapper {
return logStream;
}

private async pingFunctionApp(kuduClient: KuduClient): Promise<void> {
const requestOptions: WebResource = new WebResource();
const adminKey: string = await kuduClient.functionModel.getAdminToken();
await signRequest(requestOptions, new TokenCredentials(adminKey));
request.get(`https://${this.defaultHostName}/admin/host/status`, requestOptions);
}

private async deployZip(fsPath: string, client: WebSiteManagementClient, outputChannel: vscode.OutputChannel, configurationSectionName: string, confirmDeployment: boolean): Promise<void> {
if (confirmDeployment) {
const warning: string = localize('zipWarning', 'Are you sure you want to deploy to "{0}"? This will overwrite any previous deployment and cannot be undone.', this.appName);
Expand Down Expand Up @@ -411,3 +427,15 @@ export class SiteWrapper {
}
}
}

async function signRequest(req: WebResource, cred: ServiceClientCredentials): Promise<void> {
await new Promise((resolve: () => void, reject: (err: Error) => void): void => {
cred.signRequest(req, (err: Error) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}

0 comments on commit 0f4661b

Please sign in to comment.