|
1 |
| -// tslint:disable:no-console no-any |
2 |
| -import * as path from 'path'; |
3 |
| -import * as util from 'util'; |
4 |
| -import { createLogger, format, transports } from 'winston'; |
5 |
| -import { EXTENSION_ROOT_DIR } from '../constants'; |
6 |
| -import { sendTelemetryEvent } from '../telemetry'; |
7 |
| -import { isTestExecution } from './constants'; |
8 |
| -import { LogLevel } from './types'; |
9 |
| -import { StopWatch } from './utils/stopWatch'; |
10 |
| - |
11 |
| -// tslint:disable-next-line: no-var-requires no-require-imports |
12 |
| -const TransportStream = require('winston-transport'); |
13 |
| - |
14 |
| -// Initialize the loggers as soon as this module is imported. |
15 |
| -const consoleLogger = createLogger(); |
16 |
| -const fileLogger = createLogger(); |
17 |
| -initializeConsoleLogger(); |
18 |
| -initializeFileLogger(); |
19 |
| - |
20 |
| -const logLevelMap = { |
21 |
| - [LogLevel.Error]: 'error', |
22 |
| - [LogLevel.Information]: 'info', |
23 |
| - [LogLevel.Warning]: 'warn' |
24 |
| -}; |
25 |
| - |
26 |
| -function log(logLevel: LogLevel, ...args: any[]) { |
27 |
| - if (consoleLogger.transports.length > 0) { |
28 |
| - const message = args.length === 0 ? '' : util.format(args[0], ...args.slice(1)); |
29 |
| - consoleLogger.log(logLevelMap[logLevel], message); |
30 |
| - } |
31 |
| - logToFile(logLevel, ...args); |
32 |
| -} |
33 |
| -function logToFile(logLevel: LogLevel, ...args: any[]) { |
34 |
| - if (fileLogger.transports.length === 0) { |
35 |
| - return; |
36 |
| - } |
37 |
| - const message = args.length === 0 ? '' : util.format(args[0], ...args.slice(1)); |
38 |
| - fileLogger.log(logLevelMap[logLevel], message); |
39 |
| -} |
40 |
| - |
41 |
| -/** |
42 |
| - * Initialize the logger for console. |
43 |
| - * We do two things here: |
44 |
| - * - Anything written to the logger will be displayed in the console window as well |
45 |
| - * This is the behavior of the extension when running it. |
46 |
| - * When running tests on CI, we might not want this behavior, as it'll pollute the |
47 |
| - * test output with logging (as mentioned this is optional). |
48 |
| - * Messages logged using our logger will be prefixed with `Python Extension: ....` for console window. |
49 |
| - * This way, its easy to identify messages specific to the python extension. |
50 |
| - * - Monkey patch the console.log and similar methods to send messages to the file logger. |
51 |
| - * When running UI tests or similar, and we want to see everything that was dumped into `console window`, |
52 |
| - * then we need to hijack the console logger. |
53 |
| - * To do this we need to monkey patch the console methods. |
54 |
| - * This is optional (generally done when running tests on CI). |
55 |
| - */ |
56 |
| -// tslint:disable-next-line: max-func-body-length |
57 |
| -function initializeConsoleLogger() { |
58 |
| - const logMethods = { |
59 |
| - log: Symbol.for('log'), |
60 |
| - info: Symbol.for('info'), |
61 |
| - error: Symbol.for('error'), |
62 |
| - debug: Symbol.for('debug'), |
63 |
| - warn: Symbol.for('warn') |
64 |
| - }; |
65 |
| - |
66 |
| - function logToConsole(stream: 'info' | 'error' | 'warn' | 'log' | 'debug', ...args: any[]) { |
67 |
| - if (['info', 'error', 'warn', 'log', 'debug'].indexOf(stream) === -1) { |
68 |
| - stream = 'log'; |
69 |
| - } |
70 |
| - // Further below we monkeypatch the console.log, etc methods. |
71 |
| - const fn = (console as any)[logMethods[stream]] || console[stream] || console.log; |
72 |
| - fn(...args); |
73 |
| - } |
74 |
| - |
75 |
| - // Hijack `console.log` when running tests on CI. |
76 |
| - if (process.env.VSC_PYTHON_LOG_FILE && process.env.TF_BUILD) { |
77 |
| - /* |
78 |
| - What we're doing here is monkey patching the console.log so we can send everything sent to console window into our logs. |
79 |
| - This is only required when we're directly writing to `console.log` or not using our `winston logger`. |
80 |
| - This is something we'd generally turn on, only on CI so we can see everything logged to the console window (via the logs). |
81 |
| - */ |
82 |
| - // Keep track of the original functions before we monkey patch them. |
83 |
| - // Using symbols guarantee the properties will be unique & prevents clashing with names other code/library may create or have created. |
84 |
| - (console as any)[logMethods.log] = console.log; |
85 |
| - (console as any)[logMethods.info] = console.info; |
86 |
| - (console as any)[logMethods.error] = console.error; |
87 |
| - (console as any)[logMethods.debug] = console.debug; |
88 |
| - (console as any)[logMethods.warn] = console.warn; |
89 |
| - |
90 |
| - // tslint:disable-next-line: no-function-expression |
91 |
| - console.log = function () { |
92 |
| - const args = Array.prototype.slice.call(arguments); |
93 |
| - logToConsole('log', ...args); |
94 |
| - logToFile(LogLevel.Information, ...args); |
95 |
| - }; |
96 |
| - // tslint:disable-next-line: no-function-expression |
97 |
| - console.info = function () { |
98 |
| - const args = Array.prototype.slice.call(arguments); |
99 |
| - logToConsole('info', ...args); |
100 |
| - logToFile(LogLevel.Information, ...args); |
101 |
| - }; |
102 |
| - // tslint:disable-next-line: no-function-expression |
103 |
| - console.warn = function () { |
104 |
| - const args = Array.prototype.slice.call(arguments); |
105 |
| - logToConsole('warn', ...args); |
106 |
| - logToFile(LogLevel.Warning, ...args); |
107 |
| - }; |
108 |
| - // tslint:disable-next-line: no-function-expression |
109 |
| - console.error = function () { |
110 |
| - const args = Array.prototype.slice.call(arguments); |
111 |
| - logToConsole('error', ...args); |
112 |
| - logToFile(LogLevel.Error, ...args); |
113 |
| - }; |
114 |
| - // tslint:disable-next-line: no-function-expression |
115 |
| - console.debug = function () { |
116 |
| - const args = Array.prototype.slice.call(arguments); |
117 |
| - logToConsole('debug', ...args); |
118 |
| - logToFile(LogLevel.Information, ...args); |
119 |
| - }; |
120 |
| - } |
121 |
| - |
122 |
| - if (isTestExecution() && !process.env.VSC_PYTHON_FORCE_LOGGING) { |
123 |
| - // Do not log to console if running tests on CI and we're not asked to do so. |
124 |
| - return; |
125 |
| - } |
126 |
| - |
127 |
| - // Rest of this stuff is just to instantiate the console logger. |
128 |
| - // I.e. when we use our logger, ensure we also log to the console (for end users). |
129 |
| - const formattedMessage = Symbol.for('message'); |
130 |
| - class ConsoleTransport extends TransportStream { |
131 |
| - constructor(options?: any) { |
132 |
| - super(options); |
133 |
| - } |
134 |
| - public log?(info: { level: string; message: string; [formattedMessage]: string }, next: () => void): any { |
135 |
| - setImmediate(() => this.emit('logged', info)); |
136 |
| - logToConsole(info.level as any, info[formattedMessage] || info.message); |
137 |
| - if (next) { |
138 |
| - next(); |
139 |
| - } |
140 |
| - } |
141 |
| - } |
142 |
| - const consoleFormatter = format.printf(({ level, message, label, timestamp }) => { |
143 |
| - // If we're on CI server, no need for the label (prefix) |
144 |
| - // Pascal casing og log level, so log files get highlighted when viewing in VSC and other editors. |
145 |
| - const prefix = `${level.substring(0, 1).toUpperCase()}${level.substring(1)} ${ |
146 |
| - process.env.TF_BUILD ? '' : label |
147 |
| - }`; |
148 |
| - return `${prefix.trim()} ${timestamp}: ${message}`; |
149 |
| - }); |
150 |
| - const consoleFormat = format.combine( |
151 |
| - format.label({ label: 'Python Extension:' }), |
152 |
| - format.timestamp({ |
153 |
| - format: 'YYYY-MM-DD HH:mm:ss' |
154 |
| - }), |
155 |
| - consoleFormatter |
156 |
| - ); |
157 |
| - consoleLogger.add(new ConsoleTransport({ format: consoleFormat }) as any); |
158 |
| -} |
159 |
| - |
160 |
| -/** |
161 |
| - * Send all logging output to a log file. |
162 |
| - * We log to the file only if a file has been specified as an env variable. |
163 |
| - * Currently this is setup on CI servers. |
164 |
| - */ |
165 |
| -function initializeFileLogger() { |
166 |
| - if (!process.env.VSC_PYTHON_LOG_FILE) { |
167 |
| - return; |
168 |
| - } |
169 |
| - const fileFormatter = format.printf(({ level, message, timestamp }) => { |
170 |
| - // Pascal casing og log level, so log files get highlighted when viewing in VSC and other editors. |
171 |
| - return `${level.substring(0, 1).toUpperCase()}${level.substring(1)} ${timestamp}: ${message}`; |
172 |
| - }); |
173 |
| - const fileFormat = format.combine( |
174 |
| - format.timestamp({ |
175 |
| - format: 'YYYY-MM-DD HH:mm:ss' |
176 |
| - }), |
177 |
| - fileFormatter |
178 |
| - ); |
179 |
| - const logFilePath = path.isAbsolute(process.env.VSC_PYTHON_LOG_FILE) |
180 |
| - ? process.env.VSC_PYTHON_LOG_FILE |
181 |
| - : path.join(EXTENSION_ROOT_DIR, process.env.VSC_PYTHON_LOG_FILE); |
182 |
| - const logFileSink = new transports.File({ |
183 |
| - format: fileFormat, |
184 |
| - filename: logFilePath, |
185 |
| - handleExceptions: true |
186 |
| - }); |
187 |
| - fileLogger.add(logFileSink); |
188 |
| -} |
189 |
| - |
190 |
| -/** |
191 |
| - * What do we want to log. |
192 |
| - * @export |
193 |
| - * @enum {number} |
194 |
| - */ |
195 |
| -export enum LogOptions { |
196 |
| - None = 0, |
197 |
| - Arguments = 1, |
198 |
| - ReturnValue = 2 |
199 |
| -} |
200 |
| - |
201 |
| -// tslint:disable-next-line:no-any |
202 |
| -function argsToLogString(args: any[]): string { |
203 |
| - try { |
204 |
| - return (args || []) |
205 |
| - .map((item, index) => { |
206 |
| - if (item === undefined) { |
207 |
| - return `Arg ${index + 1}: undefined`; |
208 |
| - } |
209 |
| - if (item === null) { |
210 |
| - return `Arg ${index + 1}: null`; |
211 |
| - } |
212 |
| - try { |
213 |
| - if (item && item.fsPath) { |
214 |
| - return `Arg ${index + 1}: <Uri:${item.fsPath}>`; |
215 |
| - } |
216 |
| - return `Arg ${index + 1}: ${JSON.stringify(item)}`; |
217 |
| - } catch { |
218 |
| - return `Arg ${index + 1}: <argument cannot be serialized for logging>`; |
219 |
| - } |
220 |
| - }) |
221 |
| - .join(', '); |
222 |
| - } catch { |
223 |
| - return ''; |
224 |
| - } |
225 |
| -} |
226 |
| - |
227 |
| -// tslint:disable-next-line:no-any |
228 |
| -function returnValueToLogString(returnValue: any): string { |
229 |
| - const returnValueMessage = 'Return Value: '; |
230 |
| - if (returnValue === undefined) { |
231 |
| - return `${returnValueMessage}undefined`; |
232 |
| - } |
233 |
| - if (returnValue === null) { |
234 |
| - return `${returnValueMessage}null`; |
235 |
| - } |
236 |
| - try { |
237 |
| - return `${returnValueMessage}${JSON.stringify(returnValue)}`; |
238 |
| - } catch { |
239 |
| - return `${returnValueMessage}<Return value cannot be serialized for logging>`; |
240 |
| - } |
241 |
| -} |
242 |
| - |
243 |
| -export function traceVerbose(...args: any[]) { |
244 |
| - log(LogLevel.Information, ...args); |
245 |
| -} |
246 |
| - |
247 |
| -export function traceError(...args: any[]) { |
248 |
| - log(LogLevel.Error, ...args); |
249 |
| -} |
250 |
| - |
251 |
| -export function traceInfo(...args: any[]) { |
252 |
| - log(LogLevel.Information, ...args); |
253 |
| -} |
254 |
| - |
255 |
| -export function traceWarning(...args: any[]) { |
256 |
| - log(LogLevel.Warning, ...args); |
257 |
| -} |
258 |
| - |
259 |
| -export namespace traceDecorators { |
260 |
| - export function verbose(message: string, options: LogOptions = LogOptions.Arguments | LogOptions.ReturnValue) { |
261 |
| - return trace(message, options); |
262 |
| - } |
263 |
| - export function error(message: string) { |
264 |
| - return trace(message, LogOptions.Arguments | LogOptions.ReturnValue, LogLevel.Error); |
265 |
| - } |
266 |
| - export function info(message: string) { |
267 |
| - return trace(message); |
268 |
| - } |
269 |
| - export function warn(message: string) { |
270 |
| - return trace(message, LogOptions.Arguments | LogOptions.ReturnValue, LogLevel.Warning); |
271 |
| - } |
272 |
| -} |
273 |
| -function trace(message: string, options: LogOptions = LogOptions.None, logLevel?: LogLevel) { |
274 |
| - // tslint:disable-next-line:no-function-expression no-any |
275 |
| - return function (_: Object, __: string, descriptor: TypedPropertyDescriptor<any>) { |
276 |
| - const originalMethod = descriptor.value; |
277 |
| - // tslint:disable-next-line:no-function-expression no-any |
278 |
| - descriptor.value = function (...args: any[]) { |
279 |
| - const className = _ && _.constructor ? _.constructor.name : ''; |
280 |
| - // tslint:disable-next-line:no-any |
281 |
| - function writeSuccess(elapsedTime: number, returnValue: any) { |
282 |
| - if (logLevel === LogLevel.Error) { |
283 |
| - return; |
284 |
| - } |
285 |
| - writeToLog(elapsedTime, returnValue); |
286 |
| - } |
287 |
| - function writeError(elapsedTime: number, ex: Error) { |
288 |
| - writeToLog(elapsedTime, undefined, ex); |
289 |
| - } |
290 |
| - // tslint:disable-next-line:no-any |
291 |
| - function writeToLog(elapsedTime: number, returnValue?: any, ex?: Error) { |
292 |
| - const messagesToLog = [message]; |
293 |
| - messagesToLog.push( |
294 |
| - `Class name = ${className}, completed in ${elapsedTime}ms, has a ${ |
295 |
| - returnValue ? 'truthy' : 'falsy' |
296 |
| - } return value` |
297 |
| - ); |
298 |
| - if ((options & LogOptions.Arguments) === LogOptions.Arguments) { |
299 |
| - messagesToLog.push(argsToLogString(args)); |
300 |
| - } |
301 |
| - if ((options & LogOptions.ReturnValue) === LogOptions.ReturnValue) { |
302 |
| - messagesToLog.push(returnValueToLogString(returnValue)); |
303 |
| - } |
304 |
| - if (ex) { |
305 |
| - log(LogLevel.Error, messagesToLog.join(', '), ex); |
306 |
| - sendTelemetryEvent('ERROR' as any, undefined, undefined, ex); |
307 |
| - } else { |
308 |
| - log(LogLevel.Information, messagesToLog.join(', ')); |
309 |
| - } |
310 |
| - } |
311 |
| - const timer = new StopWatch(); |
312 |
| - try { |
313 |
| - // tslint:disable-next-line:no-invalid-this no-use-before-declare no-unsafe-any |
314 |
| - const result = originalMethod.apply(this, args); |
315 |
| - // If method being wrapped returns a promise then wait for it. |
316 |
| - // tslint:disable-next-line:no-unsafe-any |
317 |
| - if (result && typeof result.then === 'function' && typeof result.catch === 'function') { |
318 |
| - // tslint:disable-next-line:prefer-type-cast |
319 |
| - (result as Promise<void>) |
320 |
| - .then((data) => { |
321 |
| - writeSuccess(timer.elapsedTime, data); |
322 |
| - return data; |
323 |
| - }) |
324 |
| - .catch((ex) => { |
325 |
| - writeError(timer.elapsedTime, ex); |
326 |
| - }); |
327 |
| - } else { |
328 |
| - writeSuccess(timer.elapsedTime, result); |
329 |
| - } |
330 |
| - return result; |
331 |
| - } catch (ex) { |
332 |
| - writeError(timer.elapsedTime, ex); |
333 |
| - throw ex; |
334 |
| - } |
335 |
| - }; |
336 |
| - |
337 |
| - return descriptor; |
338 |
| - }; |
339 |
| -} |
| 1 | +export { |
| 2 | + // aliases: |
| 3 | + // (temporarily for backward compatibility and to avoid churn) |
| 4 | + traceDecorators, |
| 5 | + traceError, |
| 6 | + traceInfo, |
| 7 | + traceVerbose, |
| 8 | + traceWarning |
| 9 | +} from '../logging/_trace'; |
| 10 | +export { TraceOptions as LogOptions } from '../logging/types'; |
0 commit comments