/** * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; const shimmer = require('shimmer'); var urlParse = require('url').parse; function startSpanForRequest(api, req, res, next) { var originalEnd = res.end; var options = { name: urlParse(req.url).pathname, url: req.url, traceContext: req.headers[api.constants.TRACE_CONTEXT_HEADER_NAME], ip: req.connection.remoteAddress, method: req.method, skipFrames: 4 }; api.runInRootSpan(options, function(root) { // Set response trace context. var responseTraceContext = api.getResponseTraceContext(options.traceContext, !!root); if (responseTraceContext) { res.setHeader(api.constants.TRACE_CONTEXT_HEADER_NAME, responseTraceContext); } if (!root) { return; } api.wrapEmitter(req); api.wrapEmitter(res); const url = (req.headers['X-Forwarded-Proto'] || 'http') + '://' + req.headers.host + req.url; // we use the path part of the url as the span name and add the full // url as a label // req.path would be more desirable but is not set at the time our middlewear runs. root.addLabel(api.labels.HTTP_METHOD_LABEL_KEY, req.method); root.addLabel(api.labels.HTTP_URL_LABEL_KEY, url); root.addLabel(api.labels.HTTP_SOURCE_IP, req.connection.remoteAddress); // wrap end res.end = function(chunk, encoding) { res.end = originalEnd; const returned = res.end(chunk, encoding); if (req.route && req.route.path) { root.addLabel( 'koa/request.route.path', req.route.path); } root.addLabel( api.labels.HTTP_RESPONSE_CODE_LABEL_KEY, res.statusCode); root.endSpan(); return returned; }; api.wrap(next); }); } function createMiddleware(api) { return function* middleware(next) { /* jshint validthis:true */ const req = this.req; const res = this.res; startSpanForRequest(api, req, res, next); yield next; }; } function createMiddleware2x(api) { return function middleware(ctx, next) { const req = ctx.req; const res = ctx.res; startSpanForRequest(api, req, res, next); return next(); }; } function patchUse(koa, api, createMiddlewareFunction) { shimmer.wrap(koa.prototype, 'use', function useWrap(use) { return function useTrace() { if (!this._google_trace_patched) { this._google_trace_patched = true; this.use(createMiddlewareFunction(api)); } return use.apply(this, arguments); }; }); } module.exports = [ { file: '', versions: '1.x', patch: function(koa, api) { patchUse(koa, api, createMiddleware); }, unpatch: function(koa) { shimmer.unwrap(koa.prototype, 'use'); } }, { file: '', versions: '2.x', patch: function(koa, api) { patchUse(koa, api, createMiddleware2x); }, unpatch: function(koa) { shimmer.unwrap(koa.prototype, 'use'); } } ];