• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

thundra-io / thundra-agent-nodejs / addbc52d-3c22-4a09-9d80-9ba76dd88108

pending completion
addbc52d-3c22-4a09-9d80-9ba76dd88108

push

circleci

GitHub
Add support for tracing imports/requires during coldstart (#400)

302 of 545 branches covered (55.41%)

Branch coverage included in aggregate %.

19 of 19 new or added lines in 6 files covered. (100.0%)

1242 of 1903 relevant lines covered (65.27%)

24.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.47
/src/plugins/Trace.ts
1
import Utils from '../utils/Utils';
10✔
2
import TraceConfig from './config/TraceConfig';
3
import MonitoringDataType from './data/base/MonitoringDataType';
10✔
4
import ThundraSpan from '../opentracing/Span';
5
import SpanData from './data/trace/SpanData';
6
import PluginContext from './PluginContext';
7
import { INTEGRATIONS } from '../Constants';
10✔
8
import * as opentracing from 'opentracing';
10✔
9
import ThundraLogger from '../ThundraLogger';
10✔
10
import Integration from '../integrations/Integration';
11
import Instrumenter from '../opentracing/instrument/Instrumenter';
10✔
12
import ConfigProvider from '../config/ConfigProvider';
10✔
13
import ConfigNames from '../config/ConfigNames';
10✔
14
import ExecutionContext from '../context/ExecutionContext';
15
import GlobalTracer from '../opentracing/GlobalTracer';
10✔
16
import Plugin from './Plugin';
17

18
const get = require('lodash.get');
10✔
19

20
/**
21
 * The trace plugin for trace support
22
 */
23
export default class Trace implements Plugin {
10✔
24

25
    public static readonly NAME: string = 'Trace';
10✔
26

27
    pluginOrder: number = 1;
38✔
28
    pluginContext: PluginContext;
29
    hooks: {
30
        'before-invocation': (execContext: ExecutionContext) => void;
31
        'after-invocation': (execContext: ExecutionContext) => void;
32
    };
33
    config: TraceConfig;
34
    integrationsMap: Map<string, Integration>;
35
    instrumenter: Instrumenter;
36
    listeners: any[];
37

38
    constructor(config: TraceConfig) {
39
        this.hooks = {
38✔
40
            'before-invocation': this.beforeInvocation,
41
            'after-invocation': this.afterInvocation,
42
        };
43

44
        this.config = config;
38✔
45
        this.listeners = Utils.createSpanListeners();
38✔
46

47
        this.initIntegrations();
38✔
48

49
        opentracing.initGlobalTracer(new GlobalTracer());
38✔
50
    }
51

52
    /**
53
     * @inheritDoc
54
     */
55
    getName(): string {
56
        return Trace.NAME;
×
57
    }
58

59
    /**
60
     * Sets the the {@link PluginContext}
61
     * @param {PluginContext} pluginContext the {@link PluginContext}
62
     */
63
    setPluginContext = (pluginContext: PluginContext) => {
38✔
64
        this.pluginContext = pluginContext;
37✔
65
    }
66

67
    /**
68
     * Called before invocation
69
     * @param {ExecutionContext} execContext the {@link ExecutionContext}
70
     */
71
    beforeInvocation = (execContext: ExecutionContext) => {
38✔
72
        ThundraLogger.debug('<Trace> Before invocation of transaction', execContext.transactionId);
59✔
73

74
        const { executor } = this.pluginContext;
59✔
75
        const { tracer } = execContext;
59✔
76

77
        tracer.setSpanListeners(this.listeners);
59✔
78

79
        if (executor) {
59✔
80
            executor.startTrace(this.pluginContext, execContext, this.config);
59✔
81
        }
82
    }
83

84
    /**
85
     * Called after invocation
86
     * @param {ExecutionContext} execContext the {@link ExecutionContext}
87
     */
88
    afterInvocation = (execContext: ExecutionContext) => {
38✔
89
        ThundraLogger.debug('<Trace> After invocation of transaction', execContext.transactionId);
2✔
90

91
        const { apiKey, executor } = this.pluginContext;
2✔
92
        const { tracer, rootSpan } = execContext;
2✔
93

94
        if (executor) {
2✔
95
            executor.finishTrace(this.pluginContext, execContext, this.config);
2✔
96
        }
97

98
        const spanList: ThundraSpan[] = tracer.getRecorder().getSpanList();
2✔
99
        const sampler = get(this.config, 'sampler', { isSampled: () => true });
2✔
100
        const sampled = sampler.isSampled(rootSpan);
2✔
101

102
        ThundraLogger.debug('<Trace> Checked sampling of transaction', execContext.transactionId, ':', sampled);
2✔
103

104
        if (sampled) {
2✔
105
            const debugEnabled: boolean = ThundraLogger.isDebugEnabled();
2✔
106
            let runSamplerOnEach: boolean = this.config.runSamplerOnEachSpan;
2✔
107
            if (!runSamplerOnEach && sampler.sampleOnEach && typeof sampler.sampleOnEach === 'function') {
2!
108
                runSamplerOnEach = sampler.sampleOnEach();
×
109
            }
110

111
            let reportedSpanCount: number = 0;
2✔
112
            const highPrioritySpans: any[] = [];
2✔
113
            const lowPrioritySpans: any[] = [];
2✔
114

115
            for (const span of spanList) {
2✔
116
                if (span) {
4✔
117
                    if (runSamplerOnEach && !sampler.isSampled(span)) {
4!
118
                        ThundraLogger.debug(
×
119
                            `<Trace> Filtering span with name ${span.getOperationName()} due to custom sampling configuration`);
120
                        continue;
×
121
                    }
122

123
                    const spanData = this.buildSpanData(span, execContext);
4✔
124
                    const spanReportData = Utils.generateReport(spanData, apiKey);
4✔
125

126
                    // At coldstart, all the spans should be reported (there might be many module load spans to trace coldstart)
127
                    const shouldBeReported: boolean = span.isRootSpan || execContext.coldStart;
4✔
128
                    // On-going (not finished) spans have higher priority (for ex. in case of timeout)
129
                    const highPriority: boolean = !span.isFinished();
4✔
130

131
                    if (shouldBeReported) {
4!
132
                        if (debugEnabled) {
4!
133
                            ThundraLogger.debug('<Trace> Reporting span:', spanReportData);
×
134
                        }
135
                        execContext.report(spanReportData);
4✔
136
                        reportedSpanCount++;
4✔
137
                    } else {
138
                        if (highPriority) {
×
139
                            highPrioritySpans.push(spanReportData);
×
140
                        } else {
141
                            lowPrioritySpans.push(spanReportData);
×
142
                        }
143
                    }
144
                }
145
            }
146

147
            // Report high priority spans first until the max limit
148
            for (const span of highPrioritySpans) {
2✔
149
                if (reportedSpanCount >= this.config.maxSpanCount) {
×
150
                    if (debugEnabled) {
×
151
                        ThundraLogger.debug(`<Trace> Reached max span limit ${this.config.maxSpanCount}, ` +
×
152
                                            `so skipping remaining high priority spans`);
153
                    }
154
                    break;
×
155
                }
156
                if (debugEnabled) {
×
157
                    ThundraLogger.debug('<Trace> Reporting span:', span);
×
158
                }
159
                execContext.report(span);
×
160
                reportedSpanCount++;
×
161
            }
162

163
            // Then report low priority spans until the max limit
164
            for (const span of lowPrioritySpans) {
2✔
165
                if (reportedSpanCount >= this.config.maxSpanCount) {
×
166
                    if (debugEnabled) {
×
167
                        ThundraLogger.debug(`<Trace> Reached max span limit ${this.config.maxSpanCount}, ` +
×
168
                                            `so skipping remaining low priority spans`);
169
                    }
170
                    break;
×
171
                }
172
                if (debugEnabled) {
×
173
                    ThundraLogger.debug('<Trace> Reporting span:', span);
×
174
                }
175
                execContext.report(span);
×
176
                reportedSpanCount++;
×
177
            }
178
        }
179
    }
180

181
    /**
182
     * Destroys plugin
183
     */
184
    destroy(): void {
185
        // pass
186
    }
187

188
    /**
189
     * Initializes the {@link Instrumenter instrumenter}
190
     * @param glob the global
191
     */
192
    initInstrumenter(glob: NodeJS.Global): void {
193
        this.instrumenter.setGlobalFunction(glob);
×
194
    }
195

196
    /**
197
     * Instruments the given JS code.
198
     *
199
     * @param filename  name of the file
200
     * @param code      the code to be instrumented
201
     * @return {string}the instrumented code
202
     */
203
    instrument(filename: string, code: string): string {
204
        return this.instrumenter.instrument(filename, code);
×
205
    }
206

207
    private initIntegrations(): void {
208
        if (!(this.config.disableInstrumentation || ConfigProvider.get<boolean>(ConfigNames.THUNDRA_TRACE_DISABLE))) {
38✔
209
            this.integrationsMap = new Map<string, Integration>();
38✔
210

211
            for (const key of Object.keys(INTEGRATIONS)) {
38✔
212
                const integration = INTEGRATIONS[key];
570✔
213
                if (integration) {
570✔
214
                    const clazz = integration.class;
570✔
215
                    if (clazz) {
570✔
216
                        if (!this.integrationsMap.get(key)) {
570✔
217
                            if (!this.config.isIntegrationDisabled(key)) {
570!
218
                                const instance = new clazz(this.config);
570✔
219
                                this.integrationsMap.set(key, instance);
570✔
220
                            } else {
221
                                ThundraLogger.debug(`<Trace> Disabled integration ${key}`);
×
222
                            }
223
                        }
224
                    }
225
                }
226
            }
227

228
            this.instrumenter = new Instrumenter(this.config);
38✔
229
            this.instrumenter.hookModuleCompile();
38✔
230
        }
231
    }
232

233
    private buildSpanData(span: ThundraSpan, execContext: any): SpanData {
234
        const spanData = Utils.initMonitoringData(this.pluginContext, MonitoringDataType.SPAN) as SpanData;
4✔
235

236
        spanData.id = span.spanContext.spanId;
4✔
237
        spanData.traceId = execContext.traceId;
4✔
238
        spanData.transactionId = execContext.transactionId;
4✔
239
        spanData.parentSpanId = span.spanContext.parentId;
4✔
240
        spanData.spanOrder = span.order;
4✔
241
        spanData.domainName = span.domainName ? span.domainName : '';
4!
242
        spanData.className = span.className ? span.className : '';
4!
243
        spanData.serviceName = execContext.rootSpan.operationName;
4✔
244
        spanData.operationName = span.operationName;
4✔
245
        spanData.startTimestamp = span.startTime;
4✔
246
        spanData.duration = span.getDuration();
4✔
247
        spanData.finishTimestamp = span.finishTime;
4✔
248
        spanData.tags = span.tags;
4✔
249
        spanData.logs = span.logs;
4✔
250

251
        return spanData;
4✔
252
    }
253

254
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc