Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: add web framework plugin types and script to fetch types #621

Merged
merged 5 commits into from
Jan 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
*.tgz
node_modules/
npm-debug.log
.DS_Store
.nyc_output/
.vscode/
build/
node_modules/
npm-debug.log
*.tgz
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,23 @@
},
"devDependencies": {
"@google-cloud/datastore": "^1.0.2",
"@types/connect": "^3.4.31",
"@types/continuation-local-storage": "^3.2.1",
"@types/express": "^4.11.0",
"@types/extend": "^3.0.0",
"@types/glob": "^5.0.32",
"@types/hapi": "^16.1.12",
"@types/is": "0.0.18",
"@types/koa": "^2.0.43",
"@types/methods": "^1.1.0",
"@types/mocha": "^2.2.44",
"@types/ncp": "^2.0.1",
"@types/node": "^9.3.0",
"@types/once": "^1.4.0",
"@types/pify": "^3.0.0",
"@types/proxyquire": "^1.3.28",
"@types/request": "^2.0.8",
"@types/restify": "^5.0.7",
"@types/semver": "^5.4.0",
"@types/shimmer": "^1.0.0",
"@types/tmp": "0.0.33",
Expand Down
6 changes: 5 additions & 1 deletion scripts/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Stats, stat, readFile, writeFile, mkdir } from 'fs';
import { mkdir, Stats, stat, readFile, writeFile } from 'fs';
import * as glob from 'glob';
import { ncp } from 'ncp';
import * as path from 'path';
Expand All @@ -21,6 +21,10 @@ export function nodule(nodule: string) {
return path.relative(BUILD_DIRECTORY, `node_modules/${nodule}`);
}

export function flatten<T>(arr: Array<Array<T>>): Array<T> {
return arr.reduce((acc, e) => acc.concat(e), []);
}

export function existsP(path: string): Promise<boolean> {
return statP(path).then(
() => Promise.resolve(true),
Expand Down
4 changes: 2 additions & 2 deletions src/plugin-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ export interface RootSpanOptions extends SpanOptions {
url?: string;
/**
* The serialized form of an object that contains information about an
* existing trace context.
* existing trace context, if it exists.
*/
traceContext?: string;
traceContext?: string|null;
}

export interface TraceAgent {
Expand Down
89 changes: 51 additions & 38 deletions src/plugins/plugin-connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,47 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';

var urlParse = require('url').parse;
import {IncomingMessage, ServerResponse} from 'http';
import * as shimmer from 'shimmer';
import {parse as urlParse} from 'url';

var SUPPORTED_VERSIONS = '3.x';
import {PluginTypes} from '..';

function createMiddleware(api) {
return function middleware(req, res, next) {
var options = {
name: urlParse(req.originalUrl).pathname,
import {connect_3} from './types';

type Connect3 = typeof connect_3;
// Connect docs note that routed requests have an originalUrl property.
// https://github.com/senchalabs/connect/tree/3.6.5#appuseroute-fn
type Request = IncomingMessage&{originalUrl?: string};

const SUPPORTED_VERSIONS = '3.x';

function getFirstHeader(req: IncomingMessage, key: string): string|null {
let headerValue = req.headers[key] || null;
if (headerValue && typeof headerValue !== 'string') {
headerValue = headerValue[0];
}
return headerValue;
}

function createMiddleware(api: PluginTypes.TraceAgent):
connect_3.NextHandleFunction {
return function middleware(req: Request, res, next) {
const options = {
name: req.originalUrl ? (urlParse(req.originalUrl).pathname || '') : '',
url: req.originalUrl,
traceContext: req.headers[api.constants.TRACE_CONTEXT_HEADER_NAME.toLowerCase()],
traceContext:
getFirstHeader(req, api.constants.TRACE_CONTEXT_HEADER_NAME),
skipFrames: 3
};
api.runInRootSpan(options, function(root) {
api.runInRootSpan(options, (root) => {
// Set response trace context.
var responseTraceContext =
api.getResponseTraceContext(options.traceContext, !!root);
const responseTraceContext =
api.getResponseTraceContext(options.traceContext || null, !!root);
if (responseTraceContext) {
res.setHeader(api.constants.TRACE_CONTEXT_HEADER_NAME, responseTraceContext);
res.setHeader(
api.constants.TRACE_CONTEXT_HEADER_NAME, responseTraceContext);
}

if (!root) {
Expand All @@ -42,8 +63,8 @@ function createMiddleware(api) {
api.wrapEmitter(req);
api.wrapEmitter(res);

var url = (req.headers['X-Forwarded-Proto'] || 'http') +
'://' + req.headers.host + req.originalUrl;
const url = `${req.headers['X-Forwarded-Proto'] || 'http'}://${
req.headers.host}${req.originalUrl}`;

// we use the path part of the url as the span name and add the full
// url as a label
Expand All @@ -52,18 +73,13 @@ function createMiddleware(api) {
root.addLabel(api.labels.HTTP_SOURCE_IP, req.connection.remoteAddress);

// wrap end
var originalEnd = res.end;
res.end = function() {
const originalEnd = res.end;
res.end = function(this: ServerResponse) {
res.end = originalEnd;
var returned = res.end.apply(this, arguments);

if (req.route && req.route.path) {
root.addLabel(
'connect/request.route.path', req.route.path);
}
const returned = res.end.apply(this, arguments);

root.addLabel(
api.labels.HTTP_RESPONSE_CODE_LABEL_KEY, res.statusCode);
root.addLabel('connect/request.route.path', req.originalUrl);
root.addLabel(api.labels.HTTP_RESPONSE_CODE_LABEL_KEY, res.statusCode);
root.endSpan();

return returned;
Expand All @@ -74,19 +90,16 @@ function createMiddleware(api) {
};
}

module.exports = [
{
file: '',
versions: SUPPORTED_VERSIONS,
intercept: function(connect, api) {
return function() {
var app = connect();
app.use(createMiddleware(api));
return app;
};
}
const plugin: PluginTypes.Plugin = [{
file: '',
versions: SUPPORTED_VERSIONS,
intercept: (connect, api) => {
return function(this: {}) {
const app = connect();
app.use(createMiddleware(api));
return app;
};
}
];

} as PluginTypes.Intercept<Connect3>];

export default {};
export = plugin;
77 changes: 44 additions & 33 deletions src/plugins/plugin-express.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,35 @@
* limitations under the License.
*/

'use strict';
var shimmer = require('shimmer');
var methods = require('methods').concat('use', 'route', 'param', 'all');
import * as httpMethods from 'methods';
import * as shimmer from 'shimmer';

var SUPPORTED_VERSIONS = '4.x';
import {PluginTypes} from '..';

function patchModuleRoot(express, api) {
var labels = api.labels;
function applicationActionWrap(method) {
return function expressActionTrace() {
if (!this._google_trace_patched && !this._router) {
this._google_trace_patched = true;
this.use(middleware);
}
return method.apply(this, arguments);
};
}
import {express_4} from './types';

// application is an undocumented member of the express object.
type Express4Module = typeof express_4&{application: express_4.Application};

const methods = httpMethods.concat('use', 'route', 'param', 'all');

function middleware(req, res, next) {
var options = {
const SUPPORTED_VERSIONS = '4.x';

function patchModuleRoot(express: Express4Module, api: PluginTypes.TraceAgent) {
const labels = api.labels;
function middleware(
req: express_4.Request, res: express_4.Response,
next: express_4.NextFunction) {
const options: PluginTypes.RootSpanOptions = {
name: req.path,
traceContext: req.get(api.constants.TRACE_CONTEXT_HEADER_NAME),
url: req.originalUrl,
skipFrames: 3
};
api.runInRootSpan(options, function(rootSpan) {
api.runInRootSpan(options, (rootSpan) => {
// Set response trace context.
var responseTraceContext =
api.getResponseTraceContext(options.traceContext, !!rootSpan);
const responseTraceContext =
api.getResponseTraceContext(options.traceContext || null, !!rootSpan);
if (responseTraceContext) {
res.set(api.constants.TRACE_CONTEXT_HEADER_NAME, responseTraceContext);
}
Expand All @@ -55,16 +55,16 @@ function patchModuleRoot(express, api) {
api.wrapEmitter(req);
api.wrapEmitter(res);

var url = req.protocol + '://' + req.hostname + req.originalUrl;
const url = `${req.protocol}://${req.hostname}${req.originalUrl}`;
rootSpan.addLabel(labels.HTTP_METHOD_LABEL_KEY, req.method);
rootSpan.addLabel(labels.HTTP_URL_LABEL_KEY, url);
rootSpan.addLabel(labels.HTTP_SOURCE_IP, req.connection.remoteAddress);

// wrap end
var originalEnd = res.end;
res.end = function() {
const originalEnd = res.end;
res.end = function(this: express_4.Response) {
res.end = originalEnd;
var returned = res.end.apply(this, arguments);
const returned = res.end.apply(this, arguments);

if (req.route && req.route.path) {
rootSpan.addLabel('express/request.route.path', req.route.path);
Expand All @@ -78,21 +78,32 @@ function patchModuleRoot(express, api) {
});
}

methods.forEach(function(method) {
function applicationActionWrap<T extends Function>(method: T): () => T {
return function expressActionTrace(this: express_4.Application&
PluginTypes.TraceAgentExtension) {
if (!this._google_trace_patched && !this._router) {
this._google_trace_patched = true;
this.use(middleware);
}
return method.apply(this, arguments);
};
}

methods.forEach((method) => {
shimmer.wrap(express.application, method, applicationActionWrap);
});
}

function unpatchModuleRoot(express) {
methods.forEach(function(method) {
function unpatchModuleRoot(express: Express4Module) {
methods.forEach((method) => {
shimmer.unwrap(express.application, method);
});
}

module.exports = [{
versions: SUPPORTED_VERSIONS,
patch: patchModuleRoot,
unpatch: unpatchModuleRoot
}];
const plugin: PluginTypes.Plugin = [{
versions: SUPPORTED_VERSIONS,
patch: patchModuleRoot,
unpatch: unpatchModuleRoot
} as PluginTypes.Patch<Express4Module>];

export default {};
export = plugin;
Loading