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

Koa module code level visibility #107

Merged
merged 14 commits into from
Feb 15, 2022
Merged
17 changes: 17 additions & 0 deletions demo/koa-example/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict'

const Koa = require('koa')
const Router = require('@koa/router')

const app = new Koa()
const router = new Router()

router.get('/', (ctx, next) => {
ctx.body = 'Hello World'
})

app
.use(router.routes())
.use(router.allowedMethods())

app.listen(1234)
16 changes: 16 additions & 0 deletions demo/koa-example/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "koa-example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@koa/router": "^10.0.0",
"koa": "^2.13.1"
}
}
18 changes: 18 additions & 0 deletions lib/context/koa-method-descriptor-builder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Pinpoint Node.js Agent
* Copyright 2022-present NAVER Corp.
* Apache License v2.0
*/

'use strict'

class KoaMethodDescriptorBuilder {
static make(builder) {
if (!builder) {
return
}
return builder.setParameterDescriptor('(ctx, next)')
}
}

module.exports = KoaMethodDescriptorBuilder
3 changes: 3 additions & 0 deletions lib/context/method-descriptor-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ function makeFunctionName(namedGroup) {
if (computedGroups && computedGroups.length > 0 && namedGroup.methodName) {
return computedGroups.groups.functionName + namedGroup.methodName
}
if (functionName === '<computed>' && typeof namedGroup.type === 'string' && typeof namedGroup.methodName === 'string') {
return namedGroup.methodName
}
return namedGroup.functionName
}

Expand Down
5 changes: 2 additions & 3 deletions lib/instrumentation/module-hook.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const MODULES = [
'express',
'http',
'https',
// 'koa',
'koa-router',
'redis',
'ioredis',
Expand Down Expand Up @@ -67,8 +66,8 @@ class ModuleHook {
} catch (error) {
log.error('error occurred', error)
}
}
}

if (!version) {
version = process.versions.node
}
Expand Down
36 changes: 0 additions & 36 deletions lib/instrumentation/module/koa-route.js

This file was deleted.

106 changes: 58 additions & 48 deletions lib/instrumentation/module/koa-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,70 +10,80 @@ const semver = require('semver')
const shimmer = require('shimmer')
const ServiceTypeCode = require('../../constant/service-type').ServiceTypeCode
const log = require('../../utils/logger')
const MethodDescriptor = require('../../context/method-descriptor')
const layerPatchedSymbol = Symbol('PinpointKoaLayerPatched')
const { makeMethodDescriptorBuilder } = require('../../context/make-method-descriptor-builder')
const KoaMethodDescriptorBuilder = require('../../context/koa-method-descriptor-builder')
const apiMetaService = require('../../context/api-meta-service')

module.exports = function(agent, version, router) {
module.exports = function (agent, version, router) {
if (!semver.satisfies(version, '>=5.2.0 <8')) {
log.debug('koa-router version %s not supported - aborting...', version)
return router
}

const methodDescriptorMap = new Map()
shimmer.wrap(router.prototype, 'register', function (original) {
return function (path, methods, middleware, opts) {
const layer = original.apply(this, arguments)

const MODULE_NAME = 'koa'
const OBJECT_NAME = 'router'
if (layer[layerPatchedSymbol]) {
return layer
}

function getMethodDescriptor (methodName) {
return methodDescriptorMap.get(methodName)|| setMethodDescriptor(methodName)
}
layer[layerPatchedSymbol] = true

function setMethodDescriptor(methodName) {
const descriptor = MethodDescriptor.create(MODULE_NAME, OBJECT_NAME, methodName)
apiMetaService.cacheApi(descriptor)
methodDescriptorMap.set(methodName, descriptor)
return descriptor
}
if (!Array.isArray(layer.stack) || layer.stack.length < 1) {
return layer
}

shimmer.wrap(router.prototype, 'register', function(original) {
return function (path, methods, middleware, opts) {
const layer = original.apply(this, arguments)
if (layer.stack) {
layer.stack.forEach((fn, index) => {
if (typeof fn === 'function') {
let result
layer.stack[index] = async function(ctx, next) {
const name = fn.name || 'AnonymousFunction'
const trace = agent.traceContext.currentTraceObject()
let spanEventRecorder = null
try {
if (trace) {
spanEventRecorder = trace.traceBlockBegin()
spanEventRecorder.recordServiceType(ServiceTypeCode.koa)
spanEventRecorder.recordApiDesc(`koa.router.${ctx.method.toLocaleLowerCase()} [${name}]`)
}
result = await fn.apply(this, arguments)
} catch (e) {
if (!e._pinpointCheck) {
e._pinpointCheck = true
spanEventRecorder.recordServiceType(ServiceTypeCode.koa)
spanEventRecorder.recordApiDesc(`koa.router.${ctx.method.toLocaleLowerCase()} [${name}]`)
spanEventRecorder.recordException(e, true)
}
throw e
} finally {
if (trace) {
trace.traceBlockEnd(spanEventRecorder)
}
}
return result
}
const handlerIndex = layer.stack.length - 1
const fn = layer.stack[handlerIndex]
if (typeof fn !== 'function') {
return layer
}

const builder = KoaMethodDescriptorBuilder.make(makeMethodDescriptorBuilder('koa', 2, 3))
const methodDescriptor = apiMetaService.cacheApiWithBuilder(builder)
layer.stack[handlerIndex] = async function (ctx, next) {
const name = fn.name || 'AnonymousFunction'
const trace = agent.traceContext.currentTraceObject()
let spanEventRecorder = null
let result
try {
if (trace) {
spanEventRecorder = trace.traceBlockBegin()
spanEventRecorder.recordServiceType(ServiceTypeCode.koa)
recordAPI(methodDescriptor, path, spanEventRecorder, ctx, name)
}
result = await fn.apply(this, arguments)
} catch (e) {
if (!e._pinpointCheck) {
e._pinpointCheck = true
spanEventRecorder.recordServiceType(ServiceTypeCode.koa)
recordAPI(methodDescriptor, path, spanEventRecorder, ctx, name)
spanEventRecorder.recordException(e, true)
}
throw e
} finally {
if (trace) {
trace.traceBlockEnd(spanEventRecorder)
}
})
}
return result
}
return layer
}
})

return router
}

function recordAPI(methodDescriptor, path, spanEventRecorder, ctx, name) {
if (methodDescriptor && typeof path === 'string') {
spanEventRecorder.recordApiWithParameters(methodDescriptor, [path])
} else if (methodDescriptor) {
spanEventRecorder.recordApi(methodDescriptor)
} else {
spanEventRecorder.recordApiDesc(`koa.router.${ctx.method.toLocaleLowerCase()} [${name}]`)
}
}

90 changes: 1 addition & 89 deletions lib/instrumentation/module/koa.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,94 +11,6 @@ const shimmer = require('shimmer')
const ServiceTypeCode = require('../../constant/service-type').ServiceTypeCode
const log = require('../../utils/logger')

module.exports = function(agent, version, koa) {

// todo. emit 체크
shimmer.wrap(koa.prototype, 'emit', function(original) {
return function (evt, err, ctx) {
const trace = agent.traceContext.currentTraceObject()
let spanEventRecorder = null
if (evt === 'error' & ctx) {
spanEventRecorder = trace.traceBlockBegin()
spanEventRecorder.recordServiceType(ServiceTypeCode.koa)
spanEventRecorder.recordApiDesc('koa')
spanEventRecorder.recordException(err, true)
trace.traceBlockEnd(spanEventRecorder)
return original.apply(this, arguments)
}
if (trace) {
spanEventRecorder = trace.traceBlockBegin()
spanEventRecorder.recordServiceType(ServiceTypeCode.koa)
spanEventRecorder.recordApiDesc('koa') // Todo. check this
}
const result = original.apply(this, arguments)
if (trace) {
trace.traceBlockEnd(spanEventRecorder)
}
return result
}
})
shimmer.wrap(koa.prototype, 'use', function(original) {
return function wrapMiddleware(middleware) {
if (typeof middleware === 'function') {
const cb = arguments[0]
const name = middleware.name || 'AnonymousFunction'
// todo. 기본로직을 제외 하는 방법 검토할 것
// if (cb.toString().trim().match(/^async/)) {}
arguments[0] = async function(ctx, next) {
let result
const trace = agent.traceContext.currentTraceObject()
let spanEventRecorder = null
try {
if (trace) {
spanEventRecorder = trace.traceBlockBegin()
spanEventRecorder.recordServiceType(ServiceTypeCode.koa)
spanEventRecorder.recordApiDesc(`middleware [${name}]`)
}
result = await cb.apply(this, arguments)
} catch (e) {
if (!e._pinpointCheck) {
e._pinpointCheck = true
spanEventRecorder.recordServiceType(ServiceTypeCode.koa)
spanEventRecorder.recordApiDesc(`middleware [${name}]`)
spanEventRecorder.recordException(e, true)
}
throw e
} finally {
if (trace) {
trace.traceBlockEnd(spanEventRecorder)
}
}
return result
}

}
return original.apply(this, arguments)
}
})
// Test 3. createContext
// shimmer.wrap(koa.prototype, 'createContext', function(original) {
// return function wrapCreateContext(req, res) {
// const result = original.apply(this, arguments)
//
// shimmer.wrap(result, 'onerror', function(ori) {
// return function (err) {
// const trace = agent.traceContext.currentTraceObject()
// if (trace) {
// const spanEventRecorder = trace.traceBlockBegin()
// spanEventRecorder.recordServiceType(ServiceTypeCode.express)
// spanEventRecorder.recordApiDesc('tes')
// spanEventRecorder.recordException(err, true)
// trace.traceBlockEnd(spanEventRecorder)
// }
// return ori.apply(this, arguments)
// }
// })
//
//
//
// return result
// }
// })
module.exports = function (agent, version, koa) {
return koa
}
Loading