Skip to content
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
4 changes: 3 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const Scf = require('./baas/scf')
const Tag = require('./baas/tag')
const Postgresql = require('./baas/postgresql')
const Vpc = require('./baas/vpc')
const monitor = require('./monitor')

module.exports = {
Apigw,
Expand All @@ -21,5 +22,6 @@ module.exports = {
Scf,
Tag,
Postgresql,
Vpc
Vpc,
monitor
}
8 changes: 8 additions & 0 deletions src/monitor/agent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { EventEmitter } = require('events')
const util = require('util')

function Agent() {}

util.inherits(Agent, EventEmitter)

module.exports = Agent
21 changes: 21 additions & 0 deletions src/monitor/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Enumeration of module instrumentation types.
*
* @private
* @readonly
* @enum {string}
*/
const MODULE_TYPE = {
/** Web server framework module, such as Express or Koa. */
WEB_FRAMEWORK: 'web-framework',
PROXY: 'proxy'
}

exports.MODULE_TYPE = MODULE_TYPE

/**
* 请求开始时间存储key值(context内)
*/
const REUQEST_START = '__request_start__'

exports.REUQEST_START_KEY = REUQEST_START
12 changes: 12 additions & 0 deletions src/monitor/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const shimmer = require('./shimmer')
const Agent = require('./agent')

function initialize() {
const agent = new Agent()
// 封装 module的_load方法,在load时针对基础组件附加探针
shimmer.patchModule()
// 初始化一系列基础组件
shimmer.bootstrapInstrumentation(agent)
}

initialize()
65 changes: 65 additions & 0 deletions src/monitor/instrumentation/express.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const utils = require('../utils')

/**
* Express middleware generates traces where middleware are considered siblings
* (ended on 'next' invocation) and not nested. Middlware are nested below the
* routers they are mounted to.
*/

function wrapRouteMethods(route) {
const methods = ['all', 'delete', 'get', 'head', 'opts', 'post', 'put', 'patch']
utils.wrapMethod(route, methods, function(fn) {
return fn
})
}

module.exports = function initialize(agent, express) {
if (!express || !express.Router) {
return false
}

// wrapExpress4(express)
utils.wrapMethod(express.Router, 'route', function wrapRoute(fn) {
if (!utils.isFunction(fn)) {
return fn
}

return function wrappedRoute() {
const sourceRoute = fn.apply(this, arguments)
// Express should create a new route and layer every time Router#route is
// called, but just to be on the safe side, make sure we haven't wrapped
// this already.
if (!utils.isWrapped(sourceRoute, 'get')) {
wrapRouteMethods(sourceRoute)

const layer = this.stack[this.stack.length - 1]
utils.wrapMethod(layer, 'handle', function(func) {
const { route } = layer
const { path } = route
return function(request, response) {
function finish() {
response.removeListener('finish', finish)
request.removeListener('aborted', finish)
// 状态码
if (response.statusCode != null) {
const responseCode = String(response.statusCode)
if (/^\d+$/.test(responseCode)) {
const context = request.headers['x-apigateway-context']
agent.emit('responseFinish', context, request.method, path, responseCode)
}
}
}

// response结束时上报状态码和耗时
response.once('finish', finish)
request.once('aborted', finish)

const handle = func.apply(this, arguments)
return handle
}
})
}
return sourceRoute
}
})
}
28 changes: 28 additions & 0 deletions src/monitor/instrumentation/tencent-serverless-http.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const utils = require('../utils')
const { REUQEST_START_KEY } = require('../constants')
const report = require('../report')

module.exports = function initialize(agent, httpProxy) {
utils.wrapMethod(httpProxy, 'proxy', function wrapRoute(fn) {
return function(server, event, context) {
context[REUQEST_START_KEY] = Date.now()
const proxy = fn.apply(this, arguments)
return new Promise(function(resolve) {
agent.on('responseFinish', function(ctx, method, path, responseCode) {
if (ctx) {
report.reportHttp(ctx, method, path, responseCode).then(
function() {
resolve(proxy)
},
function() {
resolve(proxy)
}
)
} else {
resolve(proxy)
}
})
})
}
})
}
8 changes: 8 additions & 0 deletions src/monitor/instrumentations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { MODULE_TYPE } = require('./constants')

module.exports = function instrumentations() {
return {
express: { type: MODULE_TYPE.WEB_FRAMEWORK },
'tencent-serverless-http': { type: MODULE_TYPE.PROXY }
}
}
1 change: 1 addition & 0 deletions src/monitor/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = console
84 changes: 84 additions & 0 deletions src/monitor/report.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const { Capi } = require('@tencent-sdk/capi')
const logger = require('./logger')
const { REUQEST_START_KEY } = require('./constants')

// 字符串转16进制
function str2hex(str) {
if (str === '') {
return ''
}
const arr = []
for (let i = 0; i < str.length; i++) {
arr.push(str.charCodeAt(i).toString(16))
}
return arr.join('')
}

exports.reportHttp = async function(context, method, path, statusCode) {
try {
context = JSON.parse(decodeURIComponent(context))
path = str2hex(path)
const ServiceType = 'monitor'
const {
tencentcloud_region,
function_name: FunctionName,
function_version: Version = '$latest',
namespace: Namespace = 'default'
} = context
const environment = JSON.parse(context.environment || '{}')
const {
TENCENTCLOUD_SECRETID: SecretId,
TENCENTCLOUD_SECRETKEY: SecretKey,
TENCENTCLOUD_SESSIONTOKEN: Token,
TENCENTCLOUD_REGION: envTencentRegion,
REGION: envRegion
} = environment
const Region = tencentcloud_region || envTencentRegion || envRegion || 'ap-guangzhou'
if (!SecretId || !SecretKey) {
logger.warn('No SecretId or SecretKey in environment parameters.')
return
}
const client = new Capi({
Region,
SecretId,
SecretKey,
Token,
ServiceType
})
const commonParams = {
Version: '2018-07-24',
AnnounceInstance: `${Namespace}|${FunctionName}|${Version}`
}
const debugOptions = {
debug: false,
host: 'monitor.tencentcloudapi.com'
}

const latency = Date.now() - context[REUQEST_START_KEY]
const keyPrefix = `${method}_${path}`
const Metrics = [
{ MetricName: 'request', Value: 1 },
{ MetricName: keyPrefix, Value: 1 },
{ MetricName: 'latency', Value: latency },
{ MetricName: keyPrefix + '_latency', Value: latency },
{ MetricName: keyPrefix + '_' + statusCode, Value: 1 }
]
if (statusCode.startsWith('4')) {
Metrics.push({ MetricName: '4xx', Value: 1 })
Metrics.push({ MetricName: 'error', Value: 1 })
} else if (statusCode.startsWith('5')) {
Metrics.push({ MetricName: '5xx', Value: 1 })
Metrics.push({ MetricName: 'error', Value: 1 })
}

return client.request(
{
Action: 'PutMonitorData',
Metrics,
...commonParams
},
debugOptions,
true
)
} catch (e) {}
}
Loading