@stoplight/moleculer
Version:
Fast & powerful microservices framework for Node.JS
257 lines (221 loc) • 6.91 kB
JavaScript
/*
* moleculer
* Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer)
* MIT Licensed
*/
;
const _ = require("lodash");
const { isFunction, isPlainObject } = require("../utils");
module.exports = function TracingMiddleware(broker) {
const tracer = broker.tracer;
function tracingLocalActionMiddleware(handler, action) {
let opts = action.tracing;
if (opts === true || opts === false) opts = { enabled: !!opts };
opts = _.defaultsDeep({}, opts, { enabled: true });
if (opts.enabled) {
return function tracingLocalActionMiddleware(ctx) {
ctx.requestID = ctx.requestID || tracer.getCurrentTraceID();
ctx.parentID = ctx.parentID || tracer.getActiveSpanID();
const tags = {
callingLevel: ctx.level,
action: ctx.action
? {
name: ctx.action.name,
rawName: ctx.action.rawName
}
: null,
remoteCall: ctx.nodeID !== ctx.broker.nodeID,
callerNodeID: ctx.nodeID,
nodeID: ctx.broker.nodeID,
options: {
timeout: ctx.options.timeout,
retries: ctx.options.retries
},
requestID: ctx.requestID
};
const globalActionTags = tracer.opts.tags.action;
let actionTags;
// local action tags take precedence
if (isFunction(opts.tags)) {
actionTags = opts.tags;
} else if (!opts.tags && isFunction(globalActionTags)) {
actionTags = globalActionTags;
} else {
// By default all params are captured. This can be overridden globally and locally
actionTags = { ...{ params: true }, ...globalActionTags, ...opts.tags };
}
if (isFunction(actionTags)) {
const res = actionTags.call(ctx.service, ctx);
if (res) Object.assign(tags, res);
} else if (isPlainObject(actionTags)) {
if (actionTags.params === true)
tags.params =
ctx.params != null && isPlainObject(ctx.params)
? Object.assign({}, ctx.params)
: ctx.params;
else if (Array.isArray(actionTags.params))
tags.params = _.pick(ctx.params, actionTags.params);
if (actionTags.meta === true)
tags.meta = ctx.meta != null ? Object.assign({}, ctx.meta) : ctx.meta;
else if (Array.isArray(actionTags.meta))
tags.meta = _.pick(ctx.meta, actionTags.meta);
}
let spanName = `action '${ctx.action.name}'`;
if (opts.spanName) {
switch (typeof opts.spanName) {
case "string":
spanName = opts.spanName;
break;
case "function":
spanName = opts.spanName.call(ctx.service, ctx);
break;
}
}
const span = ctx.startSpan(spanName, {
id: ctx.id,
type: "action",
traceID: ctx.requestID,
parentID: ctx.parentID,
service: ctx.service,
sampled: ctx.tracing,
tags
});
ctx.tracing = span.sampled;
// Call the handler
return handler(ctx)
.then(res => {
const tags = {
fromCache: ctx.cachedResult
};
if (isFunction(actionTags)) {
const r = actionTags.call(ctx.service, ctx, res);
if (r) Object.assign(tags, r);
} else if (isPlainObject(actionTags)) {
if (actionTags.response === true)
tags.response =
res != null && isPlainObject(res)
? Object.assign({}, res)
: res;
else if (Array.isArray(actionTags.response))
tags.response = _.pick(res, actionTags.response);
}
span.addTags(tags);
ctx.finishSpan(span);
//ctx.duration = span.duration;
return res;
})
.catch(err => {
span.setError(err);
ctx.finishSpan(span);
throw err;
});
}.bind(this);
}
return handler;
}
function tracingLocalEventMiddleware(handler, event) {
const service = event.service;
let opts = event.tracing;
if (opts === true || opts === false) opts = { enabled: !!opts };
opts = _.defaultsDeep({}, opts, { enabled: true });
if (opts.enabled) {
return function tracingLocalEventMiddleware(ctx) {
ctx.requestID = ctx.requestID || tracer.getCurrentTraceID();
ctx.parentID = ctx.parentID || tracer.getActiveSpanID();
const tags = {
event: {
name: event.name,
group: event.group
},
eventName: ctx.eventName,
eventType: ctx.eventType,
callerNodeID: ctx.nodeID,
callingLevel: ctx.level,
remoteCall: ctx.nodeID !== broker.nodeID,
nodeID: broker.nodeID,
requestID: ctx.requestID
};
const globalEventTags = tracer.opts.tags.event;
let eventTags;
// local event tags take precedence
if (isFunction(opts.tags)) {
eventTags = opts.tags;
} else if (!opts.tags && isFunction(globalEventTags)) {
eventTags = globalEventTags;
} else {
// By default all params are captured. This can be overridden globally and locally
eventTags = { ...{ params: true }, ...globalEventTags, ...opts.tags };
}
if (isFunction(eventTags)) {
const res = eventTags.call(service, ctx);
if (res) Object.assign(tags, res);
} else if (isPlainObject(eventTags)) {
if (eventTags.params === true)
tags.params =
ctx.params != null && isPlainObject(ctx.params)
? Object.assign({}, ctx.params)
: ctx.params;
else if (Array.isArray(eventTags.params))
tags.params = _.pick(ctx.params, eventTags.params);
if (eventTags.meta === true)
tags.meta = ctx.meta != null ? Object.assign({}, ctx.meta) : ctx.meta;
else if (Array.isArray(eventTags.meta))
tags.meta = _.pick(ctx.meta, eventTags.meta);
}
let spanName = `event '${ctx.eventName}' in '${service.fullName}'`;
if (opts.spanName) {
switch (typeof opts.spanName) {
case "string":
spanName = opts.spanName;
break;
case "function":
spanName = opts.spanName.call(service, ctx);
break;
}
}
const span = ctx.startSpan(spanName, {
id: ctx.id,
type: "event",
traceID: ctx.requestID,
parentID: ctx.parentID,
service,
sampled: ctx.tracing,
tags
});
ctx.tracing = span.sampled;
// Call the handler
return handler
.apply(service, arguments)
.then(() => {
ctx.finishSpan(span);
})
.catch(err => {
span.setError(err);
ctx.finishSpan(span);
throw err;
});
}.bind(this);
}
return handler;
}
/*
function wrapRemoteTracingMiddleware(handler) {
if (this.options.tracing) {
return function tracingMiddleware(ctx) {
if (ctx.tracing == null) {
ctx.tracing = shouldTracing(ctx);
}
return handler(ctx);
}.bind(this);
}
return handler;
}*/
return {
name: "Tracing",
localAction:
broker.isTracingEnabled() && tracer.opts.actions ? tracingLocalActionMiddleware : null,
localEvent:
broker.isTracingEnabled() && tracer.opts.events ? tracingLocalEventMiddleware : null
//remoteAction: wrapRemoteTracingMiddleware
};
};