Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
143b13f
Combine the logger init functions.
ericsnowcurrently Apr 14, 2020
d6a0c53
Factor out valueToLogString().
ericsnowcurrently Apr 14, 2020
2f7461a
Move ConsoleTransport to module scope.
ericsnowcurrently Apr 14, 2020
f99c320
Factor out a multi-logger log() function.
ericsnowcurrently Apr 14, 2020
b06fcce
Make log levels more consistent.
ericsnowcurrently Apr 15, 2020
f6f0386
Drop global disable for "no-any".
ericsnowcurrently Apr 15, 2020
e9552fe
Factor out formatMessages() for use in trace().
ericsnowcurrently Apr 15, 2020
f5f79ba
Factor out LogInfo.
ericsnowcurrently May 5, 2020
9ba1d34
Factor out CallInfo.
ericsnowcurrently May 5, 2020
124d7a5
Factor out TraceInfo.
ericsnowcurrently May 5, 2020
28f2b66
Factor out logResult().
ericsnowcurrently May 5, 2020
423326c
Add isPromise().
ericsnowcurrently May 5, 2020
d9dbbca
Factor out withTrace().
ericsnowcurrently May 5, 2020
3637a40
Move trace() into the traceDecorators namespace (not exported).
ericsnowcurrently May 5, 2020
fff0248
Factor out tracing() (i.e. a "context manager").
ericsnowcurrently May 5, 2020
d112de5
Factor out a trace() decorator factory.
ericsnowcurrently May 5, 2020
318e6b2
Add normalizeCall().
ericsnowcurrently May 5, 2020
52ceb8d
Add tracing() (i.e. a "context manager").
ericsnowcurrently May 5, 2020
8a3513f
Add a TODO about re-throwing the error.
ericsnowcurrently May 6, 2020
3cd3ad2
Use a more specific type assertion.
ericsnowcurrently May 6, 2020
0748f29
Split up initialize().
ericsnowcurrently May 6, 2020
6aa4793
Move things out of types.ts.
ericsnowcurrently May 7, 2020
1bfa529
Store the console funcs on ConsoleTransport.
ericsnowcurrently May 7, 2020
5b54214
Simplify console logging.
ericsnowcurrently May 7, 2020
33760d0
Simplify better.
ericsnowcurrently May 7, 2020
b7a6e51
Compose the log message just once.
ericsnowcurrently May 7, 2020
cc6c1c4
Alias from the "public" namespace.
ericsnowcurrently May 7, 2020
9ecbff7
Factor out _global.ts.
ericsnowcurrently May 8, 2020
c328595
Combine the loggers.
ericsnowcurrently May 8, 2020
875235f
Fold _console.ts into _global.ts.
ericsnowcurrently May 8, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions src/client/common/logger.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// These are all just temporary aliases, for backward compatibility
// and to avoid churn.
export {
// aliases:
// (temporarily for backward compatibility and to avoid churn)
traceDecorators,
traceError,
traceInfo,
traceVerbose,
traceWarning
} from '../logging/_trace';
export { TraceOptions as LogOptions } from '../logging/types';
logError as traceError,
logInfo as traceInfo,
logVerbose as traceVerbose,
logWarning as traceWarning
} from '../logging';
export { TraceOptions as LogOptions } from '../logging/trace';
10 changes: 10 additions & 0 deletions src/client/common/utils/async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ export async function waitForPromise<T>(promise: Promise<T>, timeout: number): P
});
}

// tslint:disable-next-line: no-any
export function isThenable<T>(v: any): v is Thenable<T> {
return typeof v?.then === 'function';
}

// tslint:disable-next-line: no-any
export function isPromise<T>(v: any): v is Promise<T> {
return typeof v?.then === 'function' && typeof v?.catch === 'function';
}

//======================
// Deferred

Expand Down
35 changes: 35 additions & 0 deletions src/client/common/utils/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { traceError, traceVerbose } from '../logger';
import { Resource } from '../types';
import { createDeferred, Deferred } from './async';
import { getCacheKeyFromFunctionArgs, getGlobalCacheStore, InMemoryInterpreterSpecificCache } from './cacheUtils';
import { TraceInfo, tracing } from './misc';

// tslint:disable-next-line:no-require-imports no-var-requires
const _debounce = require('lodash/debounce') as typeof import('lodash/debounce');
Expand Down Expand Up @@ -225,3 +226,37 @@ export function displayProgress(title: string, location = ProgressLocation.Windo
};
};
}

// Information about a function/method call.
export type CallInfo = {
kind: string; // "Class", etc.
name: string;
// tslint:disable-next-line:no-any
args: any[];
};

// Return a decorator that traces the decorated function.
export function trace(log: (c: CallInfo, t: TraceInfo) => void) {
// tslint:disable-next-line:no-function-expression no-any
return function (_: Object, __: string, descriptor: TypedPropertyDescriptor<any>) {
const originalMethod = descriptor.value;
// tslint:disable-next-line:no-function-expression no-any
descriptor.value = function (...args: any[]) {
const call = {
kind: 'Class',
name: _ && _.constructor ? _.constructor.name : '',
args
};
// tslint:disable-next-line:no-this-assignment no-invalid-this
const scope = this;
return tracing(
// "log()"
(t) => log(call, t),
// "run()"
() => originalMethod.apply(scope, args)
);
};

return descriptor;
};
}
42 changes: 42 additions & 0 deletions src/client/common/utils/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import { Uri } from 'vscode';
import { InterpreterUri } from '../installer/types';
import { IAsyncDisposable, IDisposable, Resource } from '../types';
import { isPromise } from './async';
import { StopWatch } from './stopWatch';

// tslint:disable-next-line:no-empty
export function noop() {}
Expand Down Expand Up @@ -38,6 +40,46 @@ export async function usingAsync<T extends IAsyncDisposable, R>(
}
}

// Information about a traced function/method call.
export type TraceInfo = {
elapsed: number; // milliseconds
// Either returnValue or err will be set.
// tslint:disable-next-line:no-any
returnValue?: any;
err?: Error;
};

// Call run(), call log() with the trace info, and return the result.
export function tracing<T>(log: (t: TraceInfo) => void, run: () => T): T {
const timer = new StopWatch();
try {
// tslint:disable-next-line:no-invalid-this no-use-before-declare no-unsafe-any
const result = run();

// If method being wrapped returns a promise then wait for it.
if (isPromise(result)) {
// tslint:disable-next-line:prefer-type-cast
(result as Promise<void>)
.then((data) => {
log({ elapsed: timer.elapsedTime, returnValue: data });
return data;
})
.catch((ex) => {
log({ elapsed: timer.elapsedTime, err: ex });
// tslint:disable-next-line:no-suspicious-comment
// TODO(GH-11645) Re-throw the error like we do
// in the non-Promise case.
});
} else {
log({ elapsed: timer.elapsedTime, returnValue: result });
}
return result;
} catch (ex) {
log({ elapsed: timer.elapsedTime, err: ex });
throw ex;
}
}

/**
* Checking whether something is a Resource (Uri/undefined).
* Using `instanceof Uri` doesn't always work as the object is not an instance of Uri (at least not in tests).
Expand Down
74 changes: 0 additions & 74 deletions src/client/logging/_console.ts

This file was deleted.

157 changes: 157 additions & 0 deletions src/client/logging/_global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';

import { isCI, isTestExecution } from '../common/constants';
import { CallInfo } from '../common/utils/decorators';
import { LogLevel } from './levels';
import { configureLogger, createLogger, ILogger, LoggerConfig, logToAll } from './logger';
import { createTracingDecorator, LogInfo, TraceOptions, tracing as _tracing } from './trace';
import { Arguments } from './util';

const globalLogger = createLogger();
initialize();

/**
* Initialize the logger.
*
* For console we do two things here:
* - Anything written to the logger will be displayed in the console
* window as well This is the behavior of the extension when running
* it. When running tests on CI, we might not want this behavior, as
* it'll pollute the test output with logging (as mentioned this is
* optional). Messages logged using our logger will be prefixed with
* `Python Extension: ....` for console window. This way, its easy
* to identify messages specific to the python extension.
* - Monkey patch the console.log and similar methods to send messages
* to the file logger. When running UI tests or similar, and we want
* to see everything that was dumped into `console window`, then we
* need to hijack the console logger. To do this we need to monkey
* patch the console methods. This is optional (generally done when
* running tests on CI).
*
* For the logfile:
* - we send all logging output to a log file. We log to the file
* only if a file has been specified as an env variable. Currently
* this is setup on CI servers.
*/
function initialize() {
const config: LoggerConfig = {};
let nonConsole = false;

// Do not log to console if running tests and we're not
// asked to do so.
if (!isTestExecution() || process.env.VSC_PYTHON_FORCE_LOGGING) {
config.console = {};
// In CI there's no need for the label.
if (!isCI) {
config.console.label = 'Python Extension:';
}
}
if (process.env.VSC_PYTHON_LOG_FILE) {
config.file = {
logfile: process.env.VSC_PYTHON_LOG_FILE
};
nonConsole = true;
}
configureLogger(globalLogger, config);

if (isCI && nonConsole) {
delete config.console;
// Send console.*() to the non-console loggers.
monkeypatchConsole(
// This is a separate logger that matches our config but
// does not do any console logging.
createLogger(config)
);
}
}

// Emit a log message derived from the args to all enabled transports.
export function log(logLevel: LogLevel, ...args: Arguments) {
logToAll([globalLogger], logLevel, args);
}

// tslint:disable-next-line:no-any
export function logVerbose(...args: any[]) {
log(LogLevel.Info, ...args);
}

// tslint:disable-next-line:no-any
export function logError(...args: any[]) {
log(LogLevel.Error, ...args);
}

// tslint:disable-next-line:no-any
export function logInfo(...args: any[]) {
log(LogLevel.Info, ...args);
}

// tslint:disable-next-line:no-any
export function logWarning(...args: any[]) {
log(LogLevel.Warn, ...args);
}

// This is like a "context manager" that logs tracing info.
export function tracing<T>(info: LogInfo, run: () => T, call?: CallInfo): T {
return _tracing([globalLogger], info, run, call);
}

export namespace traceDecorators {
const DEFAULT_OPTS: TraceOptions = TraceOptions.Arguments | TraceOptions.ReturnValue;

export function verbose(message: string, opts: TraceOptions = DEFAULT_OPTS) {
return createTracingDecorator([globalLogger], { message, opts });
}
export function error(message: string) {
const opts = DEFAULT_OPTS;
const level = LogLevel.Error;
return createTracingDecorator([globalLogger], { message, opts, level });
}
export function info(message: string) {
const opts = TraceOptions.None;
return createTracingDecorator([globalLogger], { message, opts });
}
export function warn(message: string) {
const opts = DEFAULT_OPTS;
const level = LogLevel.Warn;
return createTracingDecorator([globalLogger], { message, opts, level });
}
}

// Ensure that the console functions are bound before monkeypatching.
import './transports';

/**
* What we're doing here is monkey patching the console.log so we can
* send everything sent to console window into our logs. This is only
* required when we're directly writing to `console.log` or not using
* our `winston logger`. This is something we'd generally turn on, only
* on CI so we can see everything logged to the console window
* (via the logs).
*/
function monkeypatchConsole(logger: ILogger) {
// The logging "streams" (methods) of the node console.
const streams = ['log', 'error', 'warn', 'info', 'debug', 'trace'];
const levels: { [key: string]: LogLevel } = {
error: LogLevel.Error,
warn: LogLevel.Warn
};
// tslint:disable-next-line:no-any
const consoleAny: any = console;
for (const stream of streams) {
// Using symbols guarantee the properties will be unique & prevents
// clashing with names other code/library may create or have created.
// We could use a closure but it's a bit trickier.
const sym = Symbol.for(stream);
consoleAny[sym] = consoleAny[stream];
// tslint:disable-next-line: no-function-expression
consoleAny[stream] = function () {
const args = Array.prototype.slice.call(arguments);
const fn = consoleAny[sym];
fn(...args);
const level = levels[stream] || LogLevel.Info;
logToAll([logger], level, args);
};
}
}
Loading