vulcain-corejs
Version:
Vulcain micro-service framework
255 lines • 10.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const requestContext_1 = require("../pipeline/requestContext");
const system_1 = require("../globals/system");
const annotations_1 = require("../di/annotations");
const common_1 = require("./../pipeline/common");
const common_2 = require("./common");
const os = require("os");
// Metrics use the RED method https://www.weave.works/blog/prometheus-and-kubernetes-monitoring-your-applications/
class Span {
constructor(context, kind, name, parentId) {
this.context = context;
this.kind = kind;
this.name = name;
this.tags = {};
this.commandType = "Custom";
this._logger = context.container.get(annotations_1.DefaultServiceNames.Logger);
this.startTime = Date.now();
this.startTick = process.hrtime();
this._id = {
correlationId: parentId.correlationId,
spanId: !parentId.spanId ? parentId.correlationId : this.randomTraceId(),
parentId: parentId.spanId
};
this.metrics = context.container.get(annotations_1.DefaultServiceNames.Metrics);
this.addTag("name", name);
this.addTag("domain", system_1.Service.domainName);
this.addTag("host", os.hostname());
this.addTag("correlationId", parentId.correlationId);
this.convertKind();
}
get id() {
return this._id;
}
get tracker() {
return this._tracker;
}
static createRequestTracker(context, parentId) {
return new Span(context, common_2.SpanKind.Request, system_1.Service.fullServiceName, parentId);
}
createCommandTracker(context, commandName) {
return new Span(context, common_2.SpanKind.Command, commandName, this._id);
}
createCustomTracker(context, name, tags) {
let span = new Span(context, common_2.SpanKind.Custom, name, this._id);
span.trackAction(name, tags);
return span;
}
trackAction(action, tags) {
if (!action || action.startsWith('_'))
return;
this.action = action;
let trackerFactory = this.context.container.get(annotations_1.DefaultServiceNames.RequestTracker, true);
if (trackerFactory) {
this._tracker = trackerFactory.startSpan(this, this.name, this.action);
}
this.addTag("action", action);
tags && this.addTags(tags);
this.logAction("Log", `...Action : ${action}, ${(tags && JSON.stringify(tags)) || ""}`);
if (this.kind === common_2.SpanKind.Command) {
this.addTag("span.kind", "client");
this.addTag("type", "Command");
this.logAction("BC", `Command: ${this.name}`);
}
else if (this.kind === common_2.SpanKind.Request) {
this.addTag("span.kind", "server");
this.addTag("type", "Service");
this.logAction("RR", `Request: ${this.name}`);
}
else if (this.kind === common_2.SpanKind.Task) {
this.addTag("span.kind", "server");
this.addTag("type", "Task");
this.logAction("RT", `Async task: ${this.name}`);
}
else if (this.kind === common_2.SpanKind.Event) {
this.addTag("span.kind", "consumer");
this.addTag("type", "Event");
this.logAction("RE", `Event: ${this.name}`);
}
}
convertKind() {
if (this.kind === common_2.SpanKind.Command)
return;
if (this.context.pipeline === common_1.Pipeline.AsyncTask) {
this.kind = common_2.SpanKind.Task;
this.name = "Async:" + this.name;
}
else if (this.context.pipeline === common_1.Pipeline.Event) {
this.kind = common_2.SpanKind.Event;
this.name = "Event:" + this.name;
}
}
injectHeaders(headers) {
headers(requestContext_1.VulcainHeaderNames.X_VULCAIN_PARENT_ID, this._id.spanId);
headers(requestContext_1.VulcainHeaderNames.X_VULCAIN_CORRELATION_ID, this._id.correlationId);
// TODO move this code
if (this.context.request.headers[requestContext_1.VulcainHeaderNames.X_VULCAIN_REGISTER_STUB]) {
headers(requestContext_1.VulcainHeaderNames.X_VULCAIN_REGISTER_STUB, this.context.request.headers[requestContext_1.VulcainHeaderNames.X_VULCAIN_REGISTER_STUB]);
}
if (this.context.request.headers[requestContext_1.VulcainHeaderNames.X_VULCAIN_USE_STUB]) {
headers(requestContext_1.VulcainHeaderNames.X_VULCAIN_USE_STUB, this.context.request.headers[requestContext_1.VulcainHeaderNames.X_VULCAIN_USE_STUB]);
}
}
dispose() {
this.addTag("tenant", this.context.user.tenant);
if (this.kind === common_2.SpanKind.Command)
this.endCommand();
else
this.endRequest();
if (this._tracker) {
Object.keys(this.tags).forEach(key => this._tracker.addTag(key, this.tags[key]));
this._tracker.finish();
this._tracker = null;
}
this.context = null;
this._logger = null;
}
endCommand() {
if (this.action) {
this.addTag("error", this.error ? this.error.toString() : "false");
let metricsName = `vulcain_${this.commandType.toLowerCase()}command_duration_ms`;
this.metrics.timing(metricsName, this.durationInMs, this.tags);
}
// End Command trace
this._logger && this._logger.logAction(this.context, "EC", `Command: ${this.name} completed with ${this.error ? this.error.message : 'success'}`);
}
endRequest() {
if (this.action) {
if (!this.error && this.kind === common_2.SpanKind.Request && this.context.response && this.context.response.statusCode && this.context.response.statusCode >= 400) {
this.error = new Error("Http error " + this.context.response.statusCode);
}
this.addTag("error", this.error ? this.error.toString() : "false");
this.metrics.timing("vulcain_service_duration_ms", this.durationInMs, this.tags);
}
if (this.kind === common_2.SpanKind.Request) {
this.logAction("ER", `End request status: ${(this.context.response && this.context.response.statusCode) || 200}`);
}
else if (this.kind === common_2.SpanKind.Task) {
this.logAction("ET", `Async task: ${this.name} completed with ${this.error ? this.error.message : 'success'}`);
}
else if (this.kind === common_2.SpanKind.Event) {
this.logAction("EE", `Event ${this.name} completed with ${this.error ? this.error.message : 'success'}`);
}
}
addHttpRequestTags(uri, verb) {
this.commandType = "Http";
this.addTag("http.url", uri);
this.addTag("http.method", verb);
// http.status_code
}
addProviderCommandTags(address, schema, tenant) {
this.commandType = "Database";
this.addTag("db.instance", system_1.Service.removePasswordFromUrl(address));
this.addTag("db.schema", schema);
this.addTag("db.tenant", tenant);
}
addServiceCommandTags(serviceName, serviceVersion) {
this.commandType = "Service";
this.addTag("peer.address", system_1.Service.createContainerEndpoint(serviceName, serviceVersion));
this.addTag("peer.service", "vulcain");
this.addTag("peer.service_version", serviceVersion);
this.addTag("peer.service_name", serviceName);
}
addCustomCommandTags(commandType, tags) {
this.commandType = commandType;
this.addTags(tags);
}
addTag(name, value) {
if (name && value) {
try {
if (typeof value === "object") {
value = JSON.stringify(value);
}
this.tags[name] = value;
}
catch (e) {
this.context.logError(e);
// then ignore
}
}
}
addTags(tags) {
if (!tags)
return;
Object.keys(tags)
.forEach(key => this.addTag(key, tags[key]));
}
logAction(action, message) {
this._logger.logAction(this.context, action, message);
}
/**
* Log an error
*
* @param {Error} error Error instance
* @param {string} [msg] Additional message
*
*/
logError(error, msg) {
if (!this.error) {
this.error = error; // Catch only first error
if (this._tracker) {
this._tracker.trackError(error, msg && msg());
}
}
this._logger.error(this.context, error, msg);
}
/**
* Log a message info
*
* @param {string} msg Message format (can include %s, %j ...)
* @param {...Array<string>} params Message parameters
*
*/
logInfo(msg) {
this._logger.info(this.context, msg);
if (this._tracker) {
this._tracker.log(msg());
}
}
/**
* Log a verbose message. Verbose message are enable by service configuration property : enableVerboseLog
*
* @param {any} context Current context
* @param {string} msg Message format (can include %s, %j ...)
* @param {...Array<string>} params Message parameters
*
*/
logVerbose(msg) {
const ok = this._logger.verbose(this.context, msg);
if (ok && this._tracker) {
this._tracker.log(msg());
}
}
get now() {
return this.startTime + this.durationInMs;
}
get durationInMs() {
const endTime = process.hrtime();
const secondDiff = endTime[0] - this.startTick[0];
const nanoSecondDiff = endTime[1] - this.startTick[1];
const diffInNanoSecond = secondDiff * 1e9 + nanoSecondDiff;
return diffInNanoSecond / 1e6;
}
randomTraceId() {
const digits = '0123456789abcdef';
let n = '';
for (let i = 0; i < 16; i++) {
const rand = Math.floor(Math.random() * 16);
n += digits[rand];
}
return n;
}
}
exports.Span = Span;
//# sourceMappingURL=span.js.map