Skip to content

Commit

Permalink
chore: Migrated @newrelic/koa into mainline agent repo (#2148)
Browse files Browse the repository at this point in the history
  • Loading branch information
bizob2828 authored Apr 17, 2024
2 parents 38e47d9 + 28d478b commit 33af646
Show file tree
Hide file tree
Showing 29 changed files with 3,123 additions and 244 deletions.
349 changes: 139 additions & 210 deletions THIRD_PARTY_NOTICES.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions lib/instrumentation/grpc-js/grpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

const recordExternal = require('../../metrics/recorders/http_external')
const recordHttp = require('../../metrics/recorders/http')
const specs = require('../../shim/specs')
const { TransactionSpec } = require('../../shim/specs')
const { DESTINATIONS } = require('../../config/attribute-filter')
const DESTINATION = DESTINATIONS.TRANS_EVENT | DESTINATIONS.ERROR_EVENT
const semver = require('semver')
Expand Down Expand Up @@ -149,7 +149,7 @@ function wrapRegister(shim, original) {

args[1] = shim.bindCreateTransaction(
instrumentedHandler,
new specs.TransactionSpec({ type: shim.WEB })
new TransactionSpec({ type: shim.WEB })
)

return original.apply(this, args)
Expand Down
9 changes: 9 additions & 0 deletions lib/instrumentation/http-methods.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright 2021 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'
const http = require('http')
const methodsLower = http.METHODS.map((method) => method.toLowerCase())
module.exports.METHODS = methodsLower
197 changes: 197 additions & 0 deletions lib/instrumentation/koa/instrumentation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'
const symbols = require('../../symbols')
const { MiddlewareSpec, MiddlewareMounterSpec } = require('../../shim/specs')

module.exports = function initialize(shim, Koa) {
// Koa's exports are different depending on using CJS or MJS - https://github.com/koajs/koa/issues/1513
const proto = Koa.prototype || Koa.default?.prototype

if (!shim || !Koa || !proto || Object.keys(proto).length > 1) {
shim.logger.debug(
'Koa instrumentation function called with incorrect arguments, not instrumenting.'
)
return
}

shim.setFramework(shim.KOA)

shim.wrapMiddlewareMounter(
proto,
'use',
new MiddlewareMounterSpec({
wrapper: wrapMiddleware
})
)
shim.wrapReturn(proto, 'createContext', wrapCreateContext)

// The application is used to handle unhandled errors in the application. We
// want to notice those.
shim.wrap(proto, 'emit', function wrapper(shim, original) {
return function wrappedEmit(evt, err, ctx) {
if (evt === 'error' && ctx) {
shim.noticeError(ctx.req, err)
}
return original.apply(this, arguments)
}
})
}

function wrapMiddleware(shim, middleware) {
// Skip middleware that are already wrapped.
if (shim.isWrapped(middleware)) {
return middleware
}

if (middleware.router) {
shim.logger.info(
[
'Found uninstrumented router property on Koa middleware.',
'This may indicate either an unsupported routing library is being used,',
'or a particular version of a supported library is not fully instrumented.'
].join(' ')
)
}

return shim.recordMiddleware(
middleware,
new MiddlewareSpec({
type: shim.MIDDLEWARE,
promise: true,
appendPath: true,
next: shim.LAST,
req: function getReq(shim, fn, fnName, args) {
return args[0] && args[0].req
}
})
)
}

/**
* Many of the properties on the `context` object are just aliases for the same
* property on the `request` or `response` objects. We take advantage of this
* by just intercepting the `request` or `response` property and don't touch
* the `context` property.
* See: https://github.com/koajs/koa/blob/master/lib/context.js#L186-L241
*
* @param {Shim} shim instance of shim
* @param {function} _fn createContext function
* @param {string} _fnName name of function
* @param {object} context koa ctx object
*/
function wrapCreateContext(shim, _fn, _fnName, context) {
wrapResponseBody(shim, context)
wrapMatchedRoute(shim, context)
wrapResponseStatus(shim, context)
}

function wrapResponseBody(shim, context) {
// The `context.body` and `context.response.body` properties are how users set
// the response contents. It is roughly equivalent to `res.send()` in Express.
// Under the hood, these set the `_body` property on the `context.response`.
context[symbols.koaBody] = context.response.body
context[symbols.koaBodySet] = false

Object.defineProperty(context.response, '_body', {
get: () => context[symbols.koaBody],
set: function setBody(val) {
if (!context[symbols.koaRouter]) {
shim.savePossibleTransactionName(context.req)
}
context[symbols.koaBody] = val
context[symbols.koaBodySet] = true
}
})
}

function wrapMatchedRoute(shim, context) {
context[symbols.koaMatchedRoute] = null
context[symbols.koaRouter] = false

Object.defineProperty(context, '_matchedRoute', {
get: () => context[symbols.koaMatchedRoute],
set: (val) => {
const match = getLayerForTransactionName(context)

// match should never be undefined given _matchedRoute was set
if (match) {
const currentSegment = shim.getActiveSegment()

// Segment/Transaction may be null, see:
// - https://github.com/newrelic/node-newrelic-koa/issues/32
// - https://github.com/newrelic/node-newrelic-koa/issues/33
if (currentSegment) {
const transaction = currentSegment.transaction

if (context[symbols.koaMatchedRoute]) {
transaction.nameState.popPath()
}

transaction.nameState.appendPath(match.path)
transaction.nameState.markPath()
}
}

context[symbols.koaMatchedRoute] = val
// still true if somehow match is undefined because we are
// using koa-router naming and don't want to allow default naming
context[symbols.koaRouter] = true
}
})
}

function wrapResponseStatus(shim, context) {
// Sometimes people just set `context.status` or `context.response.status`
// without setting a body. When this happens we'll want to use that as the
// response point to name the transaction. `context.status` is just an alias
// for `context.response.status` so we only wrap the latter.
const statusDescriptor = getInheritedPropertyDescriptor(context.response, 'status')
if (!statusDescriptor) {
shim.logger.debug('Failed to find status descriptor on context.response')
return
} else if (!statusDescriptor.get || !statusDescriptor.set) {
shim.logger.debug(statusDescriptor, 'Status descriptor missing getter/setter pair')
return
}

Object.defineProperty(context.response, 'status', {
get: () => statusDescriptor.get.call(context.response),
set: function setStatus(val) {
if (!context[symbols.koaBodySet] && !context[symbols.koaRouter]) {
shim.savePossibleTransactionName(context.req)
}
return statusDescriptor.set.call(this, val)
}
})
}

function getLayerForTransactionName(context) {
// Context.matched might be null
// See https://github.com/newrelic/node-newrelic-koa/pull/29
if (!context.matched) {
return null
}
for (let i = context.matched.length - 1; i >= 0; i--) {
const layer = context.matched[i]
if (layer.opts.end) {
return layer
}
}

return null
}

function getInheritedPropertyDescriptor(obj, property) {
let proto = obj
let descriptor = null
do {
descriptor = Object.getOwnPropertyDescriptor(proto, property)
proto = Object.getPrototypeOf(proto)
} while (!descriptor && proto)

return descriptor
}
35 changes: 35 additions & 0 deletions lib/instrumentation/koa/nr-hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

const InstrumentationDescriptor = require('../../instrumentation-descriptor')

module.exports = [
{
type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK,
moduleName: 'koa',
shimName: 'koa',
onRequire: require('./instrumentation')
},
{
type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK,
moduleName: 'koa-router',
shimName: 'koa',
onRequire: require('./router-instrumentation')
},
{
type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK,
moduleName: '@koa/router',
shimName: 'koa',
onRequire: require('./router-instrumentation')
},
{
type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK,
moduleName: 'koa-route',
shimName: 'koa',
onRequire: require('./route-instrumentation')
}
]
33 changes: 33 additions & 0 deletions lib/instrumentation/koa/route-instrumentation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

const { METHODS } = require('../http-methods')
const { MiddlewareSpec } = require('../../shim/specs')

module.exports = function instrumentRoute(shim, route) {
shim.setFramework(shim.KOA)

METHODS.forEach(function wrap(method) {
shim.wrap(route, method, function wrapMethod(shim, methodFn) {
return function wrappedMethod() {
const middleware = methodFn.apply(route, arguments)
return shim.recordMiddleware(
middleware,
new MiddlewareSpec({
route: arguments[0],
next: shim.LAST,
name: shim.getName(arguments[1]),
promise: true,
req: function getReq(shim, fn, fnName, args) {
return args[0] && args[0].req
}
})
)
}
})
})
}
Loading

0 comments on commit 33af646

Please sign in to comment.