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 | ;
|
18 |
|
19 | var TraceSpan = require('./trace-span.js');
|
20 | var TraceLabels = require('./trace-labels.js');
|
21 |
|
22 | // Auto-incrementing integer
|
23 | var 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 | */
|
34 | function 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 | */
|
88 | SpanData.prototype.createChildSpanData = function(name, skipFrames) {
|
89 | return new SpanData(this.agent, this.trace, name, this.span.spanId, false,
|
90 | skipFrames + 1);
|
91 | };
|
92 |
|
93 | SpanData.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 | */
|
100 | SpanData.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 | */
|
125 | function 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 |
|
143 | SpanData.nullSpan = {
|
144 | createChildSpanData: function() { return SpanData.nullSpan; },
|
145 | addLabel: function() {},
|
146 | close: function() {}
|
147 | };
|
148 |
|
149 | /**
|
150 | * Export SpanData.
|
151 | */
|
152 | module.exports = SpanData;
|