UNPKG

24.3 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6exports.createW3CTracestate = exports.setHttpHeader = exports.Span = void 0;
7const lodash_1 = __importDefault(require("lodash"));
8const serialize_error_1 = require("serialize-error");
9const traceparent_1 = __importDefault(require("traceparent"));
10const EventMessage_1 = require("./model/EventMessage");
11const Recorder_1 = require("./Recorder");
12const EventLoggingServiceClient_1 = require("./transport/EventLoggingServiceClient");
13const config_1 = __importDefault(require("./lib/config"));
14const util_1 = __importDefault(require("./lib/util"));
15const defaultRecorder = config_1.default.EVENT_LOGGER_SIDECAR_DISABLED
16 ? new Recorder_1.DefaultLoggerRecorder()
17 : new Recorder_1.DefaultSidecarRecorder(new EventLoggingServiceClient_1.EventLoggingServiceClient(config_1.default.EVENT_LOGGER_SERVER_HOST, config_1.default.EVENT_LOGGER_SERVER_PORT));
18/**
19 * A dict containing EventTypes which should be treated asynchronously
20 *
21 */
22const asyncOverrides = util_1.default.eventAsyncOverrides(config_1.default.ASYNC_OVERRIDE_EVENTS);
23class Span {
24 spanContext;
25 recorders;
26 isFinished = false;
27 /**
28 * Creates new span. Normally this is not used directly, but by a Tracer.createSpan method
29 * @param spanContext context of the new span. Service is obligatory. Depending on the rest provided values, the new span will be created as a parent or child span
30 * @param {Recorders} recorders different recorders to be used for different logging methods
31 * @param defaultTagsSetter the tags setter method can be passed here
32 */
33 constructor(spanContext, recorders, defaultTagsSetter) {
34 this.defaultTagsSetter = defaultTagsSetter ? defaultTagsSetter : this.defaultTagsSetter;
35 this.recorders = recorders ? recorders : { defaultRecorder };
36 if (!!spanContext.tags && !!spanContext.tags.tracestate) {
37 spanContext.tracestates = util_1.default.getTracestateMap(config_1.default.EVENT_LOGGER_VENDOR_PREFIX, spanContext.tags.tracestate).tracestates;
38 if (!spanContext.tracestates[config_1.default.EVENT_LOGGER_VENDOR_PREFIX]) {
39 spanContext.tracestates[config_1.default.EVENT_LOGGER_VENDOR_PREFIX] = { spanId: spanContext.spanId };
40 }
41 }
42 this.spanContext = spanContext;
43 this.defaultTagsSetter();
44 this.spanContext = Object.freeze(this.spanContext);
45 return this;
46 }
47 /**
48 * A method to set tags by default.
49 * @param message the message which tags will be extracted from
50 */
51 defaultTagsSetter(message) {
52 const w3cHeaders = getTracestate(this.spanContext);
53 if (w3cHeaders) {
54 this.setTags(Object.assign(this.spanContext.tags, w3cHeaders));
55 if (!(config_1.default.EVENT_LOGGER_VENDOR_PREFIX in this.getTracestates())) {
56 this.setTracestates(Object.assign(this.spanContext.tracestates, util_1.default.getTracestateMap(config_1.default.EVENT_LOGGER_VENDOR_PREFIX, w3cHeaders.tracestate).tracestates));
57 }
58 }
59 return this;
60 }
61 setTracestates(tracestates) {
62 let newContext = new EventMessage_1.EventTraceMetadata(this.getContext());
63 for (let key in tracestates) {
64 newContext.tracestates[key] = tracestates[key];
65 }
66 this.spanContext = Object.freeze(new EventMessage_1.EventTraceMetadata(newContext));
67 return this;
68 }
69 /**
70 * Gets trace context from the current span
71 */
72 getContext() {
73 return Object.assign({}, this.spanContext, { tags: JSON.parse(JSON.stringify(this.spanContext.tags)) });
74 }
75 /**
76 * Creates and returns new child span of the current span and changes the span service name
77 * @param service the name of the service of the new child span
78 * @param recorders the recorders which are be set to the child span. If omitted, the recorders of the parent span are used
79 */
80 getChild(service, recorders = this.recorders) {
81 try {
82 let inputTraceContext = this.getContext();
83 return new Span(new EventMessage_1.EventTraceMetadata(Object.assign({}, inputTraceContext, {
84 service,
85 spanId: undefined,
86 startTimestamp: undefined,
87 finishTimestamp: undefined,
88 parentSpanId: inputTraceContext.spanId
89 })), recorders, this.defaultTagsSetter);
90 }
91 catch (e) {
92 throw (e);
93 }
94 }
95 /**
96 * Injects trace context into a carrier with optional path.
97 * @param carrier any kind of message or other object with keys of type String.
98 * @param injectOptions type and path of the carrier. Type is not implemented yet. Path is the path to the trace context.
99 */
100 injectContextToMessage(carrier, injectOptions = {}) {
101 let result = lodash_1.default.cloneDeep(carrier);
102 let { path } = injectOptions; // type not implemented yet
103 if (carrier instanceof EventMessage_1.EventMessage || (('metadata' in carrier)))
104 path = 'metadata';
105 else if (carrier instanceof EventMessage_1.EventTraceMetadata) {
106 return Promise.resolve(this.spanContext);
107 }
108 if (!path) {
109 Object.assign(result, { trace: this.spanContext });
110 }
111 else {
112 lodash_1.default.merge(lodash_1.default.get(result, path), { trace: this.spanContext });
113 }
114 return result;
115 }
116 /**
117 * Injects trace context into a http request headers.
118 * @param request HTTP request.
119 * @param type type of the headers that will be created - 'w3c' or 'xb3'.
120 */
121 injectContextToHttpRequest(request, type = EventMessage_1.HttpRequestOptions.w3c) {
122 let result = lodash_1.default.cloneDeep(request);
123 result.headers = setHttpHeader(this.spanContext, type, result.headers);
124 return result;
125 }
126 /**
127 * Sets tags to the current span. If child span is created, the tags are passed on.
128 * @param tags key value pairs of tags. Tags can be changed on different child spans
129 */
130 setTags(tags) {
131 let newContext = new EventMessage_1.EventTraceMetadata(this.getContext());
132 for (let key in tags) {
133 if (key === 'tracestate' || key === 'traceparent')
134 continue;
135 newContext.tags[key] = tags[key];
136 }
137 this.spanContext = Object.freeze(new EventMessage_1.EventTraceMetadata(newContext));
138 return this;
139 }
140 _setTagTracestate(tags) {
141 let newContext = new EventMessage_1.EventTraceMetadata(this.getContext());
142 newContext.tags.tracestate = tags.tracestate;
143 this.spanContext = Object.freeze(new EventMessage_1.EventTraceMetadata(newContext));
144 return this;
145 }
146 /**
147 * Returns tags values
148 */
149 getTags() {
150 const { tags } = this.getContext();
151 return !!tags ? tags : {};
152 }
153 /**
154 * Sets tags, persisted in the tracestate header as key value pairs as base64 encoded string
155 * @param tags key-value pairs with tags
156 */
157 setTracestateTags(tags) {
158 this.spanContext.tracestates[config_1.default.EVENT_LOGGER_VENDOR_PREFIX] = Object.assign(this.spanContext.tracestates[config_1.default.EVENT_LOGGER_VENDOR_PREFIX], tags);
159 const { ownTraceStateString, restTraceStateString } = encodeTracestate(this.spanContext);
160 this._setTagTracestate({ tracestate: `${ownTraceStateString}${restTraceStateString}` });
161 return this;
162 }
163 /**
164 * Returns the tracestates object per vendor, as configured vendor tracestate is decoded key value pair with tags
165 */
166 getTracestates() {
167 return this.spanContext.tracestates;
168 }
169 /**
170 * Returns the tracestate tags for the configured vendor as key value pairs
171 */
172 getTracestateTags() {
173 if (config_1.default.EVENT_LOGGER_VENDOR_PREFIX in this.spanContext.tracestates) {
174 return this.spanContext.tracestates[config_1.default.EVENT_LOGGER_VENDOR_PREFIX];
175 }
176 else {
177 return {};
178 }
179 }
180 /**
181 * Finishes the current span and its trace and sends the data to the tracing framework.
182 * @param message optional parameter for a message to be passed to the tracing framework.
183 * @param finishTimestamp optional parameter for the finish time. If omitted, current time is used.
184 */
185 async finish(message, state, finishTimestamp) {
186 if (this.spanContext.finishTimestamp) {
187 return Promise.reject(new Error('span already finished'));
188 }
189 let spanContext = this._finishSpan(finishTimestamp).getContext();
190 await this.trace(message, spanContext, state);
191 return Promise.resolve(this);
192 }
193 /**
194 * Finishes the trace by adding finish timestamp to the current span.
195 * @param finishTimestamp optional parameter for the finish time. If omitted, current time is used.
196 */
197 _finishSpan(finishTimestamp) {
198 let newContext = Object.assign({}, this.spanContext);
199 if (finishTimestamp instanceof Date) {
200 newContext.finishTimestamp = finishTimestamp.toISOString(); // ISO 8601
201 }
202 else if (!finishTimestamp) {
203 newContext.finishTimestamp = (new Date()).toISOString(); // ISO 8601
204 }
205 else {
206 newContext.finishTimestamp = finishTimestamp;
207 }
208 this.spanContext = Object.freeze(new EventMessage_1.EventTraceMetadata(newContext));
209 return this;
210 }
211 /**
212 * Sends trace message to the tracing framework
213 * @param message
214 * @param spanContext optional parameter. Can be used to trace previous span. If not set, the current span context is used.
215 * @param action optional parameter for action. Defaults to 'span'
216 * @param state optional parameter for state. Defaults to 'success'
217 */
218 async trace(message, spanContext = this.spanContext, state, action) {
219 if (!message) {
220 message = new EventMessage_1.EventMessage({
221 type: 'application/json',
222 content: spanContext
223 });
224 }
225 try {
226 await this.recordMessage(message, EventMessage_1.TraceEventTypeAction.getType(), action, state);
227 this.isFinished = this.spanContext.finishTimestamp ? true : false;
228 return this;
229 }
230 catch (e) {
231 throw new Error(`Error when logging trace. ${JSON.stringify(e, null, 2)}`);
232 }
233 }
234 /**
235 * Sends audit type message to the event logging framework.
236 * @param message message to be recorded as audit event
237 * @param action optional parameter for action. Defaults to 'default'
238 * @param state optional parameter for state. Defaults to 'success'
239 */
240 async audit(message, action = EventMessage_1.AuditEventAction.default, state) {
241 let result = await this.recordMessage(message, EventMessage_1.AuditEventTypeAction.getType(), action, state);
242 return result;
243 }
244 /**
245 * Logs INFO type message.
246 * @param message if message is a string, the message is added to a message property of context of an event message.
247 * If message is not following the event framework message format, the message is added as it is to the context of an event message.
248 * If message follows the event framework message format, only the metadata is updated and if message lacks an UUID it is created.
249 * @param state optional parameter for state. Defaults to 'success'
250 */
251 async info(message, state) {
252 let { action, type } = new EventMessage_1.LogEventTypeAction(EventMessage_1.LogEventAction.info);
253 await this.recordMessage(message, type, action, state);
254 }
255 /**
256 * Logs DEBUG type message.
257 * @param message if message is a string, the message is added to a message property of context of an event message.
258 * If message is not following the event framework message format, the message is added as it is to the context of an event message.
259 * If message follows the event framework message format, only the metadata is updated and if message lacks an UUID it is created.
260 * @param state optional parameter for state. Defaults to 'success'
261 */
262 async debug(message, state) {
263 let { action, type } = new EventMessage_1.LogEventTypeAction(EventMessage_1.LogEventAction.debug);
264 await this.recordMessage(message, type, action, state);
265 }
266 /**
267 * Logs VERBOSE type message.
268 * @param message if message is a string, the message is added to a message property of context of an event message.
269 * If message is not following the event framework message format, the message is added as it is to the context of an event message.
270 * If message follows the event framework message format, only the metadata is updated and if message lacks an UUID it is created.
271 * @param state optional parameter for state. Defaults to 'success'
272 */
273 async verbose(message, state) {
274 let { action, type } = new EventMessage_1.LogEventTypeAction(EventMessage_1.LogEventAction.verbose);
275 await this.recordMessage(message, type, action, state);
276 }
277 /**
278 * Logs PERFORMANCE type message.
279 * @param message if message is a string, the message is added to a message property of context of an event message.
280 * If message is not following the event framework message format, the message is added as it is to the context of an event message.
281 * If message follows the event framework message format, only the metadata is updated and if message lacks an UUID it is created.
282 * @param state optional parameter for state. Defaults to 'success'
283 */
284 async performance(message, state) {
285 let { action, type } = new EventMessage_1.LogEventTypeAction(EventMessage_1.LogEventAction.performance);
286 await this.recordMessage(message, type, action, state);
287 }
288 /**
289 * Logs WARNING type message.
290 * @param message if message is a string, the message is added to a message property of context of an event message.
291 * If message is not following the event framework message format, the message is added as it is to the context of an event message.
292 * If message follows the event framework message format, only the metadata is updated and if message lacks an UUID it is created.
293 * @param state optional parameter for state. Defaults to 'success'
294 */
295 async warning(message, state) {
296 let { action, type } = new EventMessage_1.LogEventTypeAction(EventMessage_1.LogEventAction.warning);
297 await this.recordMessage(message, type, action, state);
298 }
299 /**
300 * Logs ERROR type message.
301 * @param message if message is a string, the message is added to a message property of context of an event message.
302 * If message is not following the event framework message format, the message is added as it is to the context of an event message.
303 * If message follows the event framework message format, only the metadata is updated and if message lacks an UUID it is created.
304 * @param state optional parameter for state. Defaults to 'success'
305 */
306 async error(message, state) {
307 let { action, type } = new EventMessage_1.LogEventTypeAction(EventMessage_1.LogEventAction.error);
308 await this.recordMessage(message, type, action, state);
309 }
310 /**
311 * Sends Event message to recorders
312 * @param message the Event message that needs to be recorded
313 * @param type type of Event
314 * @param action optional parameter for action. The default is based on type defaults
315 * @param state optional parameter for state. Defaults to 'success'
316 */
317 async recordMessage(message, type, action, state) {
318 if (this.isFinished) {
319 throw new Error('span finished. no further actions allowed');
320 }
321 let newEnvelope = this.createEventMessage(message, type, action, state);
322 let key = `${type}Recorder`;
323 let recorder = this.recorders.defaultRecorder;
324 if (this.recorders[key]) {
325 recorder = this.recorders[key];
326 }
327 if (util_1.default.shouldOverrideEvent(asyncOverrides, type)) {
328 //Don't wait for .record() to resolve, return straight away
329 recorder.record(newEnvelope, util_1.default.shouldLogToConsole(type, action));
330 return true;
331 }
332 const logResult = await recorder.record(newEnvelope, util_1.default.shouldLogToConsole(type, action));
333 if (logResult.status !== EventMessage_1.LogResponseStatus.accepted) {
334 throw new Error(`Error when recording ${type}-${action} event. status: ${logResult.status}`);
335 }
336 return logResult;
337 }
338 /**
339 * Helper function to create event message, based on message and event types, action and state.
340 */
341 createEventMessage = (message, type, _action, state = EventMessage_1.EventStateMetadata.success()) => {
342 let defaults = getDefaults(type);
343 let action = _action ? _action : defaults.action;
344 let messageToLog;
345 if (message instanceof Error) {
346 // const callsites = ErrorCallsites(message)
347 // message.__error_callsites = callsites
348 messageToLog = new EventMessage_1.EventMessage({
349 content: { error: (0, serialize_error_1.serializeError)(message) },
350 type: 'application/json'
351 });
352 }
353 else if (typeof message === 'string') {
354 messageToLog = new EventMessage_1.EventMessage({
355 content: { payload: message },
356 type: 'application/json'
357 });
358 }
359 else { // if ((typeof message === 'object') && (!(message.hasOwnProperty('content')) || !(message.hasOwnProperty('type')))) {
360 messageToLog = new EventMessage_1.EventMessage({
361 content: message,
362 type: 'application/json'
363 });
364 // } else {
365 // messageToLog = new EventMessage(<TypeEventMessage>message)
366 }
367 return Object.assign(messageToLog, {
368 metadata: {
369 event: defaults.eventMetadataCreator({
370 action,
371 state
372 }),
373 trace: this.spanContext
374 }
375 });
376 };
377}
378exports.Span = Span;
379const getDefaults = (type) => {
380 switch (type) {
381 case EventMessage_1.EventType.audit: {
382 return {
383 action: EventMessage_1.AuditEventAction.default,
384 eventMetadataCreator: EventMessage_1.EventMetadata.audit
385 };
386 }
387 case EventMessage_1.EventType.trace: {
388 return {
389 action: EventMessage_1.TraceEventAction.span,
390 eventMetadataCreator: EventMessage_1.EventMetadata.trace
391 };
392 }
393 case EventMessage_1.EventType.log: {
394 return {
395 action: EventMessage_1.LogEventAction.info,
396 eventMetadataCreator: EventMessage_1.EventMetadata.log
397 };
398 }
399 }
400 return {
401 action: EventMessage_1.NullEventAction.undefined,
402 eventMetadataCreator: EventMessage_1.EventMetadata.log
403 };
404};
405const setHttpHeader = (context, type, headers) => {
406 const { traceId, parentSpanId, spanId, flags, sampled } = context;
407 switch (type) {
408 case EventMessage_1.HttpRequestOptions.xb3: {
409 let XB3headers = {
410 'X-B3-TraceId': traceId,
411 'X-B3-SpanId': spanId,
412 'X-B3-Sampled': sampled,
413 'X-B3-Flags': flags,
414 'X-B3-Version': '0'
415 };
416 let result = parentSpanId ? Object.assign({ 'X-B3-ParentSpanId': parentSpanId }, XB3headers) : XB3headers;
417 return Object.assign(headers, result);
418 }
419 case EventMessage_1.HttpRequestOptions.w3c:
420 default: {
421 const tracestate = headers.tracestate ? createW3CTracestate(context, headers.tracestate) : (context.tags && context.tags.tracestate) ? context.tags.tracestate : null;
422 return lodash_1.default.pickBy({
423 ...headers,
424 ...{
425 traceparent: createW3Ctreaceparent(context),
426 tracestate
427 }
428 }, lodash_1.default.identity);
429 }
430 }
431};
432exports.setHttpHeader = setHttpHeader;
433const encodeTracestate = (context) => {
434 const { spanId } = context;
435 let tracestatesMap = {};
436 tracestatesMap[config_1.default.EVENT_LOGGER_VENDOR_PREFIX] = {};
437 let ownTraceStateString = '';
438 let restTraceStateString = '';
439 if ((!!context.tags && !!context.tags.tracestate)) {
440 const { tracestates, ownTraceState, restTraceState } = util_1.default.getTracestateMap(config_1.default.EVENT_LOGGER_VENDOR_PREFIX, context.tags.tracestate);
441 tracestatesMap = tracestates;
442 ownTraceStateString = ownTraceState;
443 restTraceStateString = restTraceState;
444 }
445 if (context.tracestates && context.tracestates[config_1.default.EVENT_LOGGER_VENDOR_PREFIX])
446 tracestatesMap = context.tracestates;
447 const newOpaqueValueMap = ((typeof tracestatesMap[config_1.default.EVENT_LOGGER_VENDOR_PREFIX]) === 'object')
448 ? Object.assign(tracestatesMap[config_1.default.EVENT_LOGGER_VENDOR_PREFIX], { spanId })
449 : null;
450 let opaqueValue = newOpaqueValueMap ? JSON.stringify(newOpaqueValueMap) : `{"spanId":"${spanId}"}`;
451 return { ownTraceStateString: `${config_1.default.EVENT_LOGGER_VENDOR_PREFIX}=${Buffer.from(opaqueValue).toString('base64')}`, restTraceStateString };
452};
453const createW3CTracestate = (spanContext, tracestate) => {
454 const newTracestate = encodeTracestate(spanContext).ownTraceStateString;
455 if (!tracestate && config_1.default.EVENT_LOGGER_TRACESTATE_HEADER_ENABLED) {
456 return newTracestate;
457 }
458 let tracestateArray = (tracestate.split(','));
459 let resultMap = new Map();
460 let resultArray = [];
461 let result;
462 for (let rawStates of tracestateArray) {
463 let states = rawStates.trim();
464 let [vendorRaw] = states.split('=');
465 resultMap.set(vendorRaw.trim(), states);
466 }
467 if (resultMap.has(config_1.default.EVENT_LOGGER_VENDOR_PREFIX)) {
468 resultMap.delete(config_1.default.EVENT_LOGGER_VENDOR_PREFIX);
469 for (let entry of resultMap.values()) {
470 resultArray.push(entry);
471 }
472 resultArray.unshift(newTracestate);
473 result = resultArray.join(',');
474 }
475 else {
476 tracestateArray.unshift(newTracestate);
477 result = tracestateArray.join(',');
478 }
479 return result;
480};
481exports.createW3CTracestate = createW3CTracestate;
482const createW3Ctreaceparent = (spanContext) => {
483 const { traceId, parentSpanId, spanId, flags, sampled } = spanContext;
484 const version = Buffer.alloc(1).fill(0);
485 const flagsForBuff = (flags && sampled) ? (flags | sampled) : flags ? flags : sampled ? sampled : 0x00;
486 const flagsBuffer = Buffer.alloc(1).fill(flagsForBuff);
487 const traceIdBuff = Buffer.from(traceId, 'hex');
488 const spanIdBuff = Buffer.from(spanId, 'hex');
489 const parentSpanIdBuff = parentSpanId && Buffer.from(parentSpanId, 'hex');
490 let W3CHeaders = parentSpanIdBuff
491 ? new traceparent_1.default(Buffer.concat([version, traceIdBuff, spanIdBuff, flagsBuffer, parentSpanIdBuff]))
492 : new traceparent_1.default(Buffer.concat([version, traceIdBuff, spanIdBuff, flagsBuffer]));
493 return W3CHeaders.toString();
494};
495const getTracestate = (spanContext) => {
496 let tracestate;
497 if (!!config_1.default.EVENT_LOGGER_TRACESTATE_HEADER_ENABLED || (!!spanContext.tags && !!spanContext.tags.tracestate)) {
498 let currentTracestate = undefined;
499 if (!!spanContext.tags && !!spanContext.tags.tracestate)
500 currentTracestate = spanContext.tags.tracestate;
501 tracestate = createW3CTracestate(spanContext, currentTracestate);
502 return { tracestate };
503 }
504 return false;
505};
506//# sourceMappingURL=Span.js.map
\No newline at end of file