|
16 | 16 |
|
17 | 17 | const filesLoadedBeforeTrace = Object.keys(require.cache); |
18 | 18 |
|
19 | | -// semver does not require any core modules. |
| 19 | +// This file's top-level imports must not transitively depend on modules that |
| 20 | +// do I/O, or continuation-local-storage will not work. |
20 | 21 | import * as semver from 'semver'; |
21 | | - |
22 | | -const useAH = !!process.env.GCLOUD_TRACE_NEW_CONTEXT && |
23 | | - semver.satisfies(process.version, '>=8'); |
24 | | -if (!useAH) { |
25 | | - // This should be loaded before any core modules. |
26 | | - require('continuation-local-storage'); |
27 | | -} |
28 | | - |
29 | | -import * as common from '@google-cloud/common'; |
30 | | -import {cls, TraceCLSConfig, TraceCLSMechanism} from './cls'; |
31 | | -import {Constants} from './constants'; |
32 | | -import {Config, defaultConfig, CLSMechanism} from './config'; |
| 22 | +import {Config, defaultConfig} from './config'; |
33 | 23 | import * as extend from 'extend'; |
34 | 24 | import * as path from 'path'; |
35 | 25 | import * as PluginTypes from './plugin-types'; |
36 | | -import {PluginLoaderConfig} from './trace-plugin-loader'; |
37 | | -import {pluginLoader} from './trace-plugin-loader'; |
| 26 | +import {tracing, Tracing, NormalizedConfig} from './tracing'; |
| 27 | +import {Singleton, FORCE_NEW, Forceable} from './util'; |
| 28 | +import {Constants} from './constants'; |
38 | 29 | import {TraceAgent} from './trace-api'; |
39 | | -import {traceWriter, TraceWriterConfig} from './trace-writer'; |
40 | | -import {Forceable, FORCE_NEW, packageNameFromPath} from './util'; |
41 | 30 |
|
42 | 31 | export {Config, PluginTypes}; |
43 | 32 |
|
44 | | -const traceAgent: TraceAgent = new TraceAgent('Custom Trace API'); |
45 | | - |
46 | | -const modulesLoadedBeforeTrace: string[] = []; |
47 | | -const traceModuleName = path.join('@google-cloud', 'trace-agent'); |
48 | | -for (let i = 0; i < filesLoadedBeforeTrace.length; i++) { |
49 | | - const moduleName = packageNameFromPath(filesLoadedBeforeTrace[i]); |
50 | | - if (moduleName && moduleName !== traceModuleName && |
51 | | - modulesLoadedBeforeTrace.indexOf(moduleName) === -1) { |
52 | | - modulesLoadedBeforeTrace.push(moduleName); |
53 | | - } |
54 | | -} |
55 | | - |
56 | | -interface TopLevelConfig { |
57 | | - enabled: boolean; |
58 | | - logLevel: number; |
59 | | - clsMechanism: CLSMechanism; |
60 | | -} |
61 | | - |
62 | | -// PluginLoaderConfig extends TraceAgentConfig |
63 | | -type NormalizedConfig = TraceWriterConfig&PluginLoaderConfig&TopLevelConfig; |
| 33 | +let traceAgent: TraceAgent; |
64 | 34 |
|
65 | 35 | /** |
66 | 36 | * Normalizes the user-provided configuration object by adding default values |
@@ -106,122 +76,73 @@ function initConfig(projectConfig: Forceable<Config>): |
106 | 76 | Constants.TRACE_SERVICE_LABEL_VALUE_LIMIT) { |
107 | 77 | config.maximumLabelValueSize = Constants.TRACE_SERVICE_LABEL_VALUE_LIMIT; |
108 | 78 | } |
109 | | - // Clamp the logger level. |
110 | | - if (config.logLevel < 0) { |
111 | | - config.logLevel = 0; |
112 | | - } else if (config.logLevel >= common.logger.LEVELS.length) { |
113 | | - config.logLevel = common.logger.LEVELS.length - 1; |
114 | | - } |
115 | | - return config; |
116 | | -} |
117 | 79 |
|
118 | | -/** |
119 | | - * Stops the Trace Agent. This disables the publicly exposed agent instance, |
120 | | - * as well as any instances passed to plugins. This also prevents the Trace |
121 | | - * Writer from publishing additional traces. |
122 | | - */ |
123 | | -function stop() { |
124 | | - if (pluginLoader.exists()) { |
125 | | - pluginLoader.get().deactivate(); |
126 | | - } |
127 | | - if (traceAgent && traceAgent.isActive()) { |
128 | | - traceAgent.disable(); |
129 | | - } |
130 | | - if (cls.exists()) { |
131 | | - cls.get().disable(); |
132 | | - } |
133 | | - if (traceWriter.exists()) { |
134 | | - traceWriter.get().stop(); |
| 80 | + // If the CLS mechanism is set to auto-determined, decide now what it should |
| 81 | + // be. |
| 82 | + const ahAvailable = semver.satisfies(process.version, '>=8') && |
| 83 | + process.env.GCLOUD_TRACE_NEW_CONTEXT; |
| 84 | + if (config.clsMechanism === 'auto') { |
| 85 | + config.clsMechanism = ahAvailable ? 'async-hooks' : 'async-listener'; |
135 | 86 | } |
| 87 | + |
| 88 | + return config; |
136 | 89 | } |
137 | 90 |
|
138 | 91 | /** |
139 | | - * Start the Trace agent that will make your application available for |
140 | | - * tracing with Stackdriver Trace. |
141 | | - * |
142 | | - * @param config - Trace configuration |
| 92 | + * Start the Stackdriver Trace Agent with the given configuration (if provided). |
| 93 | + * This function should only be called once, and before any other modules are |
| 94 | + * loaded. |
| 95 | + * @param config A configuration object. |
| 96 | + * @returns An object exposing functions for creating custom spans. |
143 | 97 | * |
144 | 98 | * @resource [Introductory video]{@link |
145 | 99 | * https://www.youtube.com/watch?v=NCFDqeo7AeY} |
146 | 100 | * |
147 | 101 | * @example |
148 | 102 | * trace.start(); |
149 | 103 | */ |
150 | | -export function start(projectConfig?: Config): PluginTypes.TraceAgent { |
151 | | - const config = initConfig(projectConfig || {}); |
152 | | - |
153 | | - if (traceAgent.isActive() && !config[FORCE_NEW]) { // already started. |
154 | | - throw new Error('Cannot call start on an already started agent.'); |
155 | | - } else if (traceAgent.isActive()) { |
156 | | - // For unit tests only. |
157 | | - // Undoes initialization that occurred last time start() was called. |
158 | | - stop(); |
| 104 | +export function start(config?: Config): PluginTypes.TraceAgent { |
| 105 | + const normalizedConfig = initConfig(config || {}); |
| 106 | + // Determine the preferred context propagation mechanism, as |
| 107 | + // continuation-local-storage should be loaded before any modules that do I/O. |
| 108 | + if (normalizedConfig.enabled && |
| 109 | + normalizedConfig.clsMechanism === 'async-listener') { |
| 110 | + // This is the earliest we can load continuation-local-storage. |
| 111 | + require('continuation-local-storage'); |
159 | 112 | } |
160 | 113 |
|
161 | | - if (!config.enabled) { |
162 | | - return traceAgent; |
163 | | - } |
164 | | - |
165 | | - const logger = common.logger({ |
166 | | - level: common.logger.LEVELS[config.logLevel], |
167 | | - tag: '@google-cloud/trace-agent' |
168 | | - }); |
169 | | - |
170 | | - if (modulesLoadedBeforeTrace.length > 0) { |
171 | | - logger.error( |
172 | | - 'TraceAgent#start: Tracing might not work as the following modules', |
173 | | - 'were loaded before the trace agent was initialized:', |
174 | | - `[${modulesLoadedBeforeTrace.sort().join(', ')}]`); |
175 | | - // Stop storing these entries in memory |
176 | | - filesLoadedBeforeTrace.length = 0; |
177 | | - modulesLoadedBeforeTrace.length = 0; |
| 114 | + if (!traceAgent) { |
| 115 | + traceAgent = new (require('./trace-api').TraceAgent)(); |
178 | 116 | } |
179 | 117 |
|
180 | 118 | try { |
181 | | - // Initialize context propagation mechanism. |
182 | | - const m = config.clsMechanism; |
183 | | - const clsConfig: Forceable<TraceCLSConfig> = { |
184 | | - mechanism: m === 'auto' ? (useAH ? TraceCLSMechanism.ASYNC_HOOKS : |
185 | | - TraceCLSMechanism.ASYNC_LISTENER) : |
186 | | - m as TraceCLSMechanism, |
187 | | - [FORCE_NEW]: config[FORCE_NEW] |
188 | | - }; |
189 | | - cls.create(clsConfig, logger).enable(); |
190 | | - |
191 | | - traceWriter.create(config, logger).initialize((err) => { |
192 | | - if (err) { |
193 | | - stop(); |
194 | | - } |
195 | | - }); |
196 | | - |
197 | | - traceAgent.enable(config, logger); |
198 | | - |
199 | | - pluginLoader.create(config, logger).activate(); |
200 | | - } catch (e) { |
201 | | - logger.error( |
202 | | - 'TraceAgent#start: Disabling the Trace Agent for the', |
203 | | - `following reason: ${e.message}`); |
204 | | - stop(); |
205 | | - return traceAgent; |
206 | | - } |
207 | | - |
208 | | - if (typeof config.projectId !== 'string' && |
209 | | - typeof config.projectId !== 'undefined') { |
210 | | - logger.error( |
211 | | - 'TraceAgent#start: config.projectId, if provided, must be a string.', |
212 | | - 'Disabling trace agent.'); |
213 | | - stop(); |
| 119 | + let tracing: Tracing; |
| 120 | + try { |
| 121 | + tracing = |
| 122 | + require('./tracing').tracing.create(normalizedConfig, traceAgent); |
| 123 | + } catch (e) { |
| 124 | + // An error could be thrown if create() is called multiple times. |
| 125 | + // It's not a helpful error message for the end user, so make it more |
| 126 | + // useful here. |
| 127 | + throw new Error('Cannot call start on an already created agent.'); |
| 128 | + } |
| 129 | + tracing.enable(); |
| 130 | + tracing.logModulesLoadedBeforeTrace(filesLoadedBeforeTrace); |
214 | 131 | return traceAgent; |
| 132 | + } finally { |
| 133 | + // Stop storing these entries in memory |
| 134 | + filesLoadedBeforeTrace.length = 0; |
215 | 135 | } |
216 | | - |
217 | | - // Make trace agent available globally without requiring package |
218 | | - global._google_trace_agent = traceAgent; |
219 | | - |
220 | | - logger.info('TraceAgent#start: Trace Agent activated.'); |
221 | | - return traceAgent; |
222 | 136 | } |
223 | 137 |
|
| 138 | +/** |
| 139 | + * Get the previously created TraceAgent object. |
| 140 | + * @returns An object exposing functions for creating custom spans. |
| 141 | + */ |
224 | 142 | export function get(): PluginTypes.TraceAgent { |
| 143 | + if (!traceAgent) { |
| 144 | + traceAgent = new (require('./trace-api').TraceAgent)(); |
| 145 | + } |
225 | 146 | return traceAgent; |
226 | 147 | } |
227 | 148 |
|
|
0 commit comments