diff --git a/config.js b/config.js index 02fb3293d..8938d721c 100644 --- a/config.js +++ b/config.js @@ -42,11 +42,12 @@ module.exports = { // This field is experimental. plugins: { 'connect': path.join(__dirname, 'src/plugins/plugin-connect.js'), + 'hapi': path.join(__dirname, 'src/plugins/plugin-hapi.js'), 'koa': path.join(__dirname, 'src/plugins/plugin-koa.js'), 'mongodb-core': path.join(__dirname, 'src/plugins/plugin-mongodb-core.js'), - 'redis': path.join(__dirname, 'src/plugins/plugin-redis.js'), - 'restify': path.join(__dirname, 'src/plugins/plugin-restify.js'), 'mysql': path.join(__dirname, 'src/plugins/plugin-mysql.js'), + 'redis': path.join(__dirname, 'src/plugins/plugin-redis.js'), + 'restify': path.join(__dirname, 'src/plugins/plugin-restify.js') }, // Valid entries are: diff --git a/src/hooks/index.js b/src/hooks/index.js index 953a967ef..5a9d7ff36 100644 --- a/src/hooks/index.js +++ b/src/hooks/index.js @@ -41,8 +41,6 @@ var toInstrument = Object.create(null, { patches: {} } }, 'grpc': { enumerable: true, value: { file: './userspace/hook-grpc.js', patches: {} } }, - 'hapi': { enumerable: true, value: { file: './userspace/hook-hapi.js', - patches: {} } }, 'http': { enumerable: true, value: { file: './core/hook-http.js', patches: {} } } }); diff --git a/src/hooks/userspace/hook-hapi.js b/src/hooks/userspace/hook-hapi.js deleted file mode 100644 index bd9aac326..000000000 --- a/src/hooks/userspace/hook-hapi.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * 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'; - -var cls = require('../../cls.js'); -var TraceLabels = require('../../trace-labels.js'); -var shimmer = require('shimmer'); -var semver = require('semver'); -var constants = require('../../constants.js'); -var urlParse = require('url').parse; -var agent; - -var SUPPORTED_VERSIONS = '8 - 16'; - -function connectionWrap(connection) { - return function connectionTrace() { - var server = connection.apply(this, arguments); - server.ext('onRequest', middleware); - return server; - }; -} - -function middleware(request, reply) { - var namespace = cls.getNamespace(); - if (!namespace) { - agent.logger.info('Hapi: no namespace found, ignoring request'); - return reply.continue(); - } - var req = request.raw.req; - var res = request.raw.res; - var traceHeader = agent.parseContextFromHeader( - req.headers[constants.TRACE_CONTEXT_HEADER_NAME]) || {}; - if (!agent.shouldTrace(req.url, traceHeader.options)) { - return reply.continue(); - } - - namespace.bindEmitter(req); - namespace.bindEmitter(res); - - var originalEnd = res.end; - - namespace.run(function() { - var rootContext = startRootSpanForRequest(req, traceHeader); - var context = agent.generateTraceContext(rootContext, true); - if (context) { - res.setHeader(constants.TRACE_CONTEXT_HEADER_NAME, context); - } else { - agent.logger.warn('hapi: Attempted to generate trace context for nullSpan'); - } - - // wrap end - res.end = function(chunk, encoding) { - res.end = originalEnd; - var returned = res.end(chunk, encoding); - - endRootSpanForRequest(rootContext, req, res); - return returned; - }; - - return reply.continue(); - }); -} - -/** - * Creates and sets up a new root span for the given request. - * @param {Object} req The request being processed. - * @param {Object} traceHeader The incoming trace header. - * @returns {!SpanData} The new initialized trace span data instance. - */ -function startRootSpanForRequest(req, traceHeader) { - var traceId = traceHeader.traceId; - var parentSpanId = traceHeader.spanId; - var 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. - var rootContext = agent.createRootSpanData(urlParse(req.url).pathname, traceId, parentSpanId, 3); - rootContext.addLabel(TraceLabels.HTTP_METHOD_LABEL_KEY, req.method); - rootContext.addLabel(TraceLabels.HTTP_URL_LABEL_KEY, url); - rootContext.addLabel(TraceLabels.HTTP_SOURCE_IP, req.connection.remoteAddress); - return rootContext; -} - - -/** - * Ends the root span for the given request. - * @param {!SpanData} rootContext The trace context to close out. - * @param {Object} req The request being processed. - * @param {Object} res The response being processed. - */ -function endRootSpanForRequest(rootContext, req, res) { - if (req.route && req.route.path) { - rootContext.addLabel( - 'hapi/request.route.path', req.route.path); - } - rootContext.addLabel( - TraceLabels.HTTP_RESPONSE_CODE_LABEL_KEY, res.statusCode); - rootContext.close(); -} - -module.exports = function(version_, agent_) { - if (!semver.satisfies(version_, SUPPORTED_VERSIONS)) { - agent_.logger.info('Hapi: unsupported version ' + version_ + ' loaded'); - return {}; - } - return { - // An empty relative path here matches the root module being loaded. - '': { - patch: function(hapi) { - agent = agent_; - shimmer.wrap(hapi.Server.prototype, 'connection', connectionWrap); - }, - unpatch: function(hapi) { - shimmer.unwrap(hapi.Server.prototype, 'connection'); - agent_.logger.info('Hapi: unpatched'); - } - } - }; -}; diff --git a/src/plugins/plugin-hapi.js b/src/plugins/plugin-hapi.js new file mode 100644 index 000000000..1fe5e272c --- /dev/null +++ b/src/plugins/plugin-hapi.js @@ -0,0 +1,94 @@ +/** + * 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'; + +var shimmer = require('shimmer'); +var urlParse = require('url').parse; + +var SUPPORTED_VERSIONS = '8 - 16'; + +function connectionWrap(api, connection) { + return function connectionTrace() { + var server = connection.apply(this, arguments); + server.ext('onRequest', middleware.bind(null, api)); + return server; + }; +} + +function middleware(api, request, reply) { + var req = request.raw.req; + var res = request.raw.res; + var originalEnd = res.end; + var options = { + name: urlParse(req.url).pathname, + traceContext: req.headers[api.constants.TRACE_CONTEXT_HEADER_NAME], + skipFrames: 3 + }; + api.runInRootSpan(options, function(root) { + if (!root) { + return reply.continue(); + } + + api.wrapEmitter(req); + api.wrapEmitter(res); + + var 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); + + var context = root.getTraceContext(); + res.setHeader(api.constants.TRACE_CONTEXT_HEADER_NAME, context); + + // wrap end + res.end = function(chunk, encoding) { + res.end = originalEnd; + var returned = res.end(chunk, encoding); + + if (req.route && req.route.path) { + root.addLabel( + 'hapi/request.route.path', req.route.path); + } + root.addLabel( + api.labels.HTTP_RESPONSE_CODE_LABEL_KEY, res.statusCode); + root.endSpan(); + + return returned; + }; + + return reply.continue(); + }); +} + +module.exports = [ + { + file: '', + versions: SUPPORTED_VERSIONS, + patch: function(hapi, api) { + shimmer.wrap(hapi.Server.prototype, + 'connection', + connectionWrap.bind(null, api)); + }, + unpatch: function(hapi) { + shimmer.unwrap(hapi.Server.prototype, 'connection'); + } + } +];