UNPKG

4.91 kBJavaScriptView Raw
1/**
2 * Copyright 2015 Google Inc. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17'use strict';
18
19var TraceSpan = require('./trace-span.js');
20var TraceLabels = require('./trace-labels.js');
21
22// Auto-incrementing integer
23var uid = 1;
24
25/**
26 * Creates a trace context object.
27 * @param {Trace} trace The object holding the spans comprising this trace.
28 * @param {string} name The name of the span.
29 * @param {number} parentSpanId The id of the parent span, 0 for root spans.
30 * @param {boolean} isRoot Whether this is a root span.
31 * @param {number} skipFrames the number of frames to remove from the top of the stack.
32 * @constructor
33 */
34function SpanData(agent, trace, name, parentSpanId, isRoot, skipFrames) {
35 var spanId = uid++;
36 this.agent = agent;
37 this.span = new TraceSpan(name, spanId, parentSpanId);
38 this.trace = trace;
39 this.isRoot = isRoot;
40 trace.spans.push(this.span);
41 if (agent.config().stackTraceLimit > 0) {
42 // This is a mechanism to get the structured stack trace out of V8.
43 // prepareStackTrace is called th first time the Error#stack property is
44 // accessed. The original behavior is to format the stack as an exception
45 // throw, which is not what we like. We customize it.
46 //
47 // See: https://code.google.com/p/v8-wiki/wiki/JavaScriptStackTraceApi
48 //
49 var origLimit = Error.stackTraceLimit;
50 Error.stackTraceLimit = agent.config().stackTraceLimit + skipFrames;
51
52 var origPrepare = Error.prepareStackTrace;
53 Error.prepareStackTrace = function(error, structured) {
54 return structured;
55 };
56 var e = {};
57 Error.captureStackTrace(e, SpanData);
58
59 var stackFrames = [];
60 e.stack.forEach(function(callSite, i) {
61 if (i < skipFrames) {
62 return;
63 }
64 var functionName = callSite.getFunctionName();
65 var methodName = callSite.getMethodName();
66 var name = (methodName && functionName) ?
67 functionName + ' [as ' + methodName + ']' :
68 functionName || methodName || '<anonymous function>';
69 stackFrames.push(new StackFrame(undefined, name,
70 callSite.getFileName(), callSite.getLineNumber(),
71 callSite.getColumnNumber()));
72 });
73 this.span.setLabel(TraceLabels.STACK_TRACE_DETAILS_KEY,
74 JSON.stringify({stack_frame: stackFrames}));
75
76 Error.stackTraceLimit = origLimit;
77 Error.prepareStackTrace = origPrepare;
78 }
79}
80
81/**
82 * Creates a child span of this span.
83 * @param name The name of the child span.
84 * @param {number} skipFrames The number of caller frames to eliminate from
85 * stack traces.
86 * @returns {SpanData} The new child trace span data.
87 */
88SpanData.prototype.createChildSpanData = function(name, skipFrames) {
89 return new SpanData(this.agent, this.trace, name, this.span.spanId, false,
90 skipFrames + 1);
91};
92
93SpanData.prototype.addLabel = function(key, value) {
94 this.span.setLabel(key, value);
95};
96
97/**
98 * Closes the span and queues it for publishing if it is a root.
99 */
100SpanData.prototype.close = function() {
101 this.span.close();
102 if (this.isRoot) {
103 this.agent.logger.info('Writing root span');
104 this.agent.traceWriter.writeSpan(this);
105 }
106};
107
108/**
109 * Trace API expects stack frames to be a JSON string with the following
110 * structure:
111 * STACK_TRACE := { "stack_frame" : [ FRAMES ] }
112 * FRAMES := { "class_name" : CLASS_NAME, "file_name" : FILE_NAME,
113 * "line_number" : LINE_NUMBER, "method_name" : METHOD_NAME }*
114 *
115 * While the API doesn't expect a columnNumber at this point, it does accept,
116 * and ignore it.
117 *
118 * @param {string|undefined} className
119 * @param {string|undefined} methodName
120 * @param {string|undefined} fileName
121 * @param {number|undefined} lineNumber
122 * @param {number|undefined} columnNumber
123 * @constructor @private
124 */
125function StackFrame(className, methodName, fileName, lineNumber, columnNumber) {
126 if (className) {
127 this.class_name = className;
128 }
129 if (methodName) {
130 this.method_name = methodName;
131 }
132 if (fileName) {
133 this.file_name = fileName;
134 }
135 if (typeof lineNumber === 'number') {
136 this.line_number = lineNumber;
137 }
138 if (typeof columnNumber === 'number') {
139 this.column_number = columnNumber;
140 }
141}
142
143SpanData.nullSpan = {
144 createChildSpanData: function() { return SpanData.nullSpan; },
145 addLabel: function() {},
146 close: function() {}
147};
148
149/**
150 * Export SpanData.
151 */
152module.exports = SpanData;