Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add logging API #9436

Merged
merged 12 commits into from Jul 23, 2019
Merged
12 changes: 12 additions & 0 deletions declarations/WebpackOptions.d.ts
Expand Up @@ -1343,6 +1343,18 @@ export interface StatsOptions {
* add the hash of the compilation
*/
hash?: boolean;
/**
* Include debug logging of specified modules (plugins/loaders). Filters can be Strings, RegExps or Functions
*/
includeDebugLogging?: FilterTypes;
/**
* add logging output
*/
logging?: boolean | ("error" | "warn" | "info" | "log" | "verbose");
/**
* add stack traces to logging output
*/
loggingTrace?: boolean;
/**
* Set the maximum number of modules to be shown
*/
Expand Down
75 changes: 75 additions & 0 deletions lib/Compilation.js
Expand Up @@ -36,6 +36,8 @@ const SortableSet = require("./util/SortableSet");
const GraphHelpers = require("./GraphHelpers");
const ModuleDependency = require("./dependencies/ModuleDependency");
const compareLocations = require("./compareLocations");
const { Logger, LogType } = require("./logging/Logger");
const ErrorHelpers = require("./ErrorHelpers");

/** @typedef {import("./Module")} Module */
/** @typedef {import("./Compiler")} Compiler */
Expand Down Expand Up @@ -95,6 +97,13 @@ const compareLocations = require("./compareLocations");
* @property {DependenciesBlockVariable[]} variables
*/

/**
* @typedef {Object} LogEntry
* @property {string} type
* @property {any[]} args
* @property {string[]=} trace
*/

/**
* @param {Chunk} a first chunk to sort by id
* @param {Chunk} b second chunk to sort by id
Expand Down Expand Up @@ -384,6 +393,9 @@ class Compilation extends Tapable {
"compilerIndex"
]),

/** @type {SyncBailHook<string, LogEntry>} */
log: new SyncBailHook(["origin", "logEntry"]),

// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
/** @type {SyncHook<object, Module>} */
Expand Down Expand Up @@ -469,6 +481,8 @@ class Compilation extends Tapable {
this.warnings = [];
/** @type {Compilation[]} */
this.children = [];
/** @type {Map<string, LogEntry[]>} */
this.logging = new Map();
/** @type {Map<DepConstructor, ModuleFactory>} */
this.dependencyFactories = new Map();
/** @type {Map<DepConstructor, DependencyTemplate>} */
Expand Down Expand Up @@ -499,6 +513,67 @@ class Compilation extends Tapable {
return new Stats(this);
}

/**
* @param {string | (function(): string)} name name of the logger, or function called once to get the logger name
* @returns {Logger} a logger with that name
*/
getLogger(name) {
if (!name) {
throw new TypeError("Compilation.getLogger(name) called without a name");
}
let logEntries;
return new Logger((type, args) => {
if (typeof name === "function") {
name = name();
if (!name) {
throw new TypeError(
"Compilation.getLogger(name) called with a function not returning a name"
);
}
}
let trace;
switch (type) {
case LogType.profile:
// eslint-disable-next-line node/no-unsupported-features/node-builtins
if (typeof console.profile === "function") {
// eslint-disable-next-line node/no-unsupported-features/node-builtins
console.profile(`[${name}] ${args[0]}`);
}
return;
case LogType.profileEnd:
// eslint-disable-next-line node/no-unsupported-features/node-builtins
if (typeof console.profileEnd === "function") {
// eslint-disable-next-line node/no-unsupported-features/node-builtins
console.profileEnd(`[${name}] ${args[0]}`);
}
return;
case LogType.warn:
case LogType.error:
case LogType.trace:
trace = ErrorHelpers.cutOffLoaderExecution(new Error("Trace").stack)
.split("\n")
.slice(3);
break;
}
const logEntry = {
time: Date.now(),
type,
args,
trace
};
if (this.hooks.log.call(name, logEntry) === undefined) {
if (logEntries === undefined) {
logEntries = this.logging.get(name);
if (logEntries === undefined) {
logEntries = [];
this.logging.set(name, logEntries);
}
}
logEntries.push(logEntry);
}
});
}

/**
* @typedef {Object} AddModuleResult
* @property {Module} module the added or existing module
Expand Down
32 changes: 32 additions & 0 deletions lib/Compiler.js
Expand Up @@ -27,6 +27,8 @@ const ResolverFactory = require("./ResolverFactory");
const RequestShortener = require("./RequestShortener");
const { makePathsRelative } = require("./util/identifier");
const ConcurrentCompilationError = require("./ConcurrentCompilationError");
const { Logger, LogType } = require("./logging/Logger");
const logToConsole = require("./logging/logToConsole");

/** @typedef {import("../declarations/WebpackOptions").Entry} Entry */
/** @typedef {import("../declarations/WebpackOptions").WebpackOptions} WebpackOptions */
Expand Down Expand Up @@ -84,6 +86,9 @@ class Compiler extends Tapable {
/** @type {SyncHook} */
watchClose: new SyncHook([]),

/** @type {SyncBailHook<string, string, any[]>} */
log: new SyncBailHook(["origin", "type", "args"]),

// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
/** @type {SyncHook} */
Expand Down Expand Up @@ -196,6 +201,33 @@ class Compiler extends Tapable {
this._assetEmittingWrittenFiles = new Map();
}

/**
* @param {string | (function(): string)} name name of the logger, or function called once to get the logger name
* @returns {Logger} a logger with that name
*/
getInfrastructureLogger(name) {
if (!name) {
throw new TypeError(
"Compiler.getInfrastructureLogger(name) called without a name"
);
}
return new Logger((type, args) => {
if (typeof name === "function") {
name = name();
if (!name) {
throw new TypeError(
"Compiler.getInfrastructureLogger(name) called with a function not returning a name"
);
}
}
if (this.hooks.log.call(name, type, args) === undefined) {
// ignore debug logging by default
if (type === LogType.debug) return;
logToConsole(name, type, args);
}
});
}

watch(watchOptions, handler) {
if (this.running) return handler(new ConcurrentCompilationError());

Expand Down
19 changes: 15 additions & 4 deletions lib/NormalModule.js
Expand Up @@ -147,30 +147,41 @@ class NormalModule extends Module {

createLoaderContext(resolver, options, compilation, fs) {
const requestShortener = compilation.runtimeTemplate.requestShortener;
const getCurrentLoaderName = () => {
const currentLoader = this.getCurrentLoader(loaderContext);
if (!currentLoader) return "(not in loader scope)";
return requestShortener.shorten(currentLoader.loader);
};
const loaderContext = {
version: 2,
emitWarning: warning => {
if (!(warning instanceof Error)) {
warning = new NonErrorEmittedError(warning);
}
const currentLoader = this.getCurrentLoader(loaderContext);
this.warnings.push(
new ModuleWarning(this, warning, {
from: requestShortener.shorten(currentLoader.loader)
from: getCurrentLoaderName()
})
);
},
emitError: error => {
if (!(error instanceof Error)) {
error = new NonErrorEmittedError(error);
}
const currentLoader = this.getCurrentLoader(loaderContext);
this.errors.push(
new ModuleError(this, error, {
from: requestShortener.shorten(currentLoader.loader)
from: getCurrentLoaderName()
})
);
},
getLogger: name => {
const currentLoader = this.getCurrentLoader(loaderContext);
return compilation.getLogger(() =>
[currentLoader && currentLoader.loader, name, this.identifier()]
.filter(Boolean)
.join(" ")
);
},
// TODO remove in webpack 5
exec: (code, filename) => {
// @ts-ignore Argument of type 'this' is not assignable to parameter of type 'Module'.
Expand Down