Add `--logs all` and `--logs error`
MODIFY
internal/cli-reporter/Reporter.ts
internal/cli-reporter/Reporter.ts
@@ -225,24 +225,30 @@ export default class Reporter implements ReporterNamespace {
public attachConditionalStream(
stream: ReporterStream,
- check: () => boolean,
+ check?: () => boolean,
): ReporterConditionalStream {
let handle: undefined | ReporterStreamHandle;
const cond: ReporterConditionalStream = {
- update: () => {
- if (check()) {
+ enable: () => {
if (handle === undefined) {
handle = this.addStream(stream);
}
- return true;
- } else {
+ },
+ disable: () => {
if (handle !== undefined) {
handle.remove();
handle = undefined;
}
- return false;
+ },
+ update: () => {
+ if (check !== undefined && check()) {
+ cond.enable();
+ } else {
+ cond.disable();
}
+
+ return handle !== undefined;
},
};

MODIFY
internal/cli-reporter/types.ts
internal/cli-reporter/types.ts
@@ -79,6 +79,8 @@ export interface ReporterNamespace {
}
export type ReporterConditionalStream = {
+ enable: () => void;
+ disable: () => void;
update: () => boolean;
};

MODIFY
internal/cli/cli.ts
internal/cli/cli.ts
import {
Client,
ClientFlags,
+ ClientLogsLevel,
ClientRequestFlags,
ClientTerminalFeatures,
DEFAULT_CLIENT_FLAGS,
@@ -43,7 +44,7 @@ import {loadUserConfig} from "@internal/core/common/userConfig";
import {RSERObject} from "@internal/codec-binary-serial";
type CLIFlags = {
- logs: boolean;
+ logs: undefined | ClientLogsLevel;
logWorkers: undefined | boolean;
logPath: undefined | AbsoluteFilePath;
markersPath: undefined | AbsoluteFilePath;
@@ -212,7 +213,7 @@ export default async function cli() {
{
description: markup`Output server logs`,
},
- ).asBoolean(false),
+ ).asStringSetOrVoid(["all", "error"]),
logWorkers: c.get(
"logWorkers",
{
@@ -420,7 +421,7 @@ export default async function cli() {
description: markup`view the logs stream`,
callback() {
overrideCLIFlags = {
- logs: true,
+ logs: "all",
};
commandFlags = {
@@ -456,8 +457,11 @@ export default async function cli() {
}
// Force logs when logPath or logWorkers is set
- if (cliFlags.logPath !== undefined || cliFlags.logWorkers === true) {
- cliFlags.logs = true;
+ if (
+ cliFlags.logs === undefined &&
+ (cliFlags.logPath !== undefined || cliFlags.logWorkers === true)
+ ) {
+ cliFlags.logs = "error";
}
const userConfig = await loadUserConfig();
@@ -531,6 +535,7 @@ export default async function cli() {
}
await client.subscribeLogs(
+ cliFlags.logs,
cliFlags.logWorkers === true,
(chunk) => {
if (fileout === undefined) {

MODIFY
internal/core/client/Client.ts
internal/core/client/Client.ts
import {
ClientFlags,
+ ClientLogsLevel,
ClientTerminalFeatures,
DEFAULT_CLIENT_FLAGS,
} from "../common/types/client";
@@ -32,7 +33,10 @@ import {Reporter, ReporterDerivedStreams} from "@internal/cli-reporter";
import prettyFormat from "@internal/pretty-format";
import {TarWriter} from "@internal/codec-tar";
import {Profile, Profiler, Trace, TraceEvent} from "@internal/v8";
-import {PartialServerQueryRequest} from "../common/bridges/ServerBridge";
+import {
+ PartialServerQueryRequest,
+ ServerBridgeLog,
+} from "../common/bridges/ServerBridge";
import {UserConfig, getUserConfigFile} from "../common/userConfig";
import {createWriteStream, removeFile} from "@internal/fs";
import {json} from "@internal/codec-config";
@@ -315,18 +319,29 @@ export default class Client {
}
public subscribeLogs(
- includeWorkerLogs: boolean,
+ level: ClientLogsLevel,
+ includeWorker: boolean,
callback: (chunk: string) => void,
): Promise<EventSubscription> {
return this.onBridge(async ({bridge}) => {
- if (includeWorkerLogs) {
- await bridge.events.enableWorkerLogs.call();
- }
+ await bridge.events.setLogLevel.call({
+ level,
+ includeWorker,
+ });
- return bridge.events.log.subscribe(({origin, chunk}) => {
- if (origin === "worker" && !includeWorkerLogs) {
- // We allow multiple calls to bridge.enableWorkerLogs
+ return bridge.events.log.subscribe((
+ {
+ origin,
+ chunk,
+ isError,
+ }: ServerBridgeLog,
+ ) => {
+ // We allow multiple calls to bridge.subscribeLogs
// Filter the event if necessary if it wasn't requested by this log subscription
+ if (origin === "worker" && !includeWorker) {
+ return;
+ }
+ if (!isError && level !== "all") {
return;
}
@@ -421,6 +436,7 @@ export default class Client {
let logsHTML = "";
let logsPlain = "";
await this.subscribeLogs(
+ "all",
true,
(chunk) => {
logsPlain += joinMarkupLines(

MODIFY
internal/core/common/bridges/ServerBridge.ts
internal/core/common/bridges/ServerBridge.ts
import {Profile} from "@internal/v8";
import {Diagnostics} from "@internal/diagnostics";
-import {ClientFlags, ClientRequestFlags} from "../types/client";
+import {
+ ClientFlags,
+ ClientLogsLevel,
+ ClientRequestFlags,
+} from "../types/client";
import {ReporterStream, ReporterStreamState} from "@internal/cli-reporter";
import {ServerMarker} from "../../server/Server";
import {TerminalFeatures} from "@internal/cli-environment";
@@ -91,6 +95,12 @@ export type ServerBridgeInfo = {
flags: ClientFlags;
};
+export type ServerBridgeLog = {
+ isError: boolean;
+ origin: "server" | "worker";
+ chunk: string;
+};
+
export default createBridge({
debugName: "server",
@@ -100,18 +110,18 @@ export default createBridge({
getClientInfo: createBridgeEventDeclaration<void, ServerBridgeInfo>(),
serverReady: createBridgeEventDeclaration<void, void>(),
write: createBridgeEventDeclaration<[string, boolean], void>(),
- log: createBridgeEventDeclaration<
- {
- origin: "server" | "worker";
- chunk: string;
- },
- void
- >(),
+ log: createBridgeEventDeclaration<ServerBridgeLog, void>(),
lspFromServerBuffer: createBridgeEventDeclaration<string, void>(),
},
server: {
- enableWorkerLogs: createBridgeEventDeclaration<void, void>(),
+ setLogLevel: createBridgeEventDeclaration<
+ {
+ level: undefined | ClientLogsLevel;
+ includeWorker: boolean;
+ },
+ void
+ >(),
endServer: createBridgeEventDeclaration<void, void>(),
updateFeatures: createBridgeEventDeclaration<TerminalFeatures, void>(),
query: createBridgeEventDeclaration<

MODIFY
internal/core/common/bridges/WorkerBridge.ts
internal/core/common/bridges/WorkerBridge.ts
@@ -15,7 +15,7 @@ import {
TransformStageName,
} from "@internal/compiler";
import {Profile} from "@internal/v8";
-import {ProfilingStartData} from "./ServerBridge";
+import {ProfilingStartData, ServerBridgeLog} from "./ServerBridge";
import {
DiagnosticIntegrity,
DiagnosticSuppressions,
@@ -158,11 +158,13 @@ export default createBridge({
shared: {},
server: {
- log: createBridgeEventDeclaration<string, void>(),
+ log: createBridgeEventDeclaration<Omit<ServerBridgeLog, "origin">, void>(),
fatalError: createBridgeEventDeclaration<BridgeErrorResponseDetails, void>(),
},
client: {
+ setLogs: createBridgeEventDeclaration<boolean, void>(),
+
updateProjects: createBridgeEventDeclaration<
{
projects: WorkerProjects;

MODIFY
internal/core/common/types/client.ts
internal/core/common/types/client.ts
@@ -64,3 +64,5 @@ export type ClientFlags = {
realCwd: AbsoluteFilePath;
silent: boolean;
};
+
+export type ClientLogsLevel = "all" | "error";

MODIFY
internal/core/common/utils/Logger.ts
internal/core/common/utils/Logger.ts
* LICENSE file in the root directory of this source tree.
*/
-import {
- Reporter,
- ReporterConditionalStream,
- ReporterOptions,
-} from "@internal/cli-reporter";
-import {DEFAULT_TERMINAL_FEATURES} from "@internal/cli-environment";
+import {Reporter, ReporterOptions} from "@internal/cli-reporter";
import {AnyMarkup, markup} from "@internal/markup";
import workerThreads = require("worker_threads");
export default class Logger extends Reporter {
- constructor(
- opts: ReporterOptions,
- {loggerType, write, check}: {
- check: () => boolean;
- write: (chunk: string) => void;
- loggerType: string;
- },
- ) {
+ constructor(opts: ReporterOptions, loggerType: string) {
super(opts);
this.loggerType = loggerType;
-
- this.conditionalStream = this.attachConditionalStream(
- {
- format: "markup",
- features: {
- ...DEFAULT_TERMINAL_FEATURES,
- columns: undefined,
- },
- write,
- },
- check,
- );
}
- private conditionalStream: ReporterConditionalStream;
private loggerType: string;
- public updateStream() {
- this.conditionalStream.update();
- }
-
protected getMessagePrefix(): AnyMarkup {
const inner = `${this.loggerType} ${process.pid}:${workerThreads.threadId}`;
const timestamp = new Date().toISOString();

MODIFY
internal/core/server/Server.ts
internal/core/server/Server.ts
@@ -40,6 +40,7 @@ import MemoryFileSystem from "./fs/MemoryFileSystem";
import ServerCache from "./ServerCache";
import {
Reporter,
+ ReporterConditionalStream,
ReporterProgress,
ReporterProgressOptions,
mergeProgresses,
@@ -48,9 +49,11 @@ import {Profiler} from "@internal/v8";
import {
PartialServerQueryRequest,
ProfilingStartData,
+ ServerBridgeLog,
} from "../common/bridges/ServerBridge";
import {
ClientFlags,
+ ClientLogsLevel,
ClientRequestFlags,
DEFAULT_CLIENT_REQUEST_FLAGS,
} from "../common/types/client";
@@ -62,7 +65,7 @@ import VirtualModules from "../common/VirtualModules";
import {DiagnosticsProcessorOptions} from "@internal/diagnostics/DiagnosticsProcessor";
import {toKebabCase} from "@internal/string-utils";
import {FilePathLocker} from "../../async/lockers";
-import {getEnvVar} from "@internal/cli-environment";
+import {DEFAULT_TERMINAL_FEATURES, getEnvVar} from "@internal/cli-environment";
import {markup} from "@internal/markup";
import prettyFormat from "@internal/pretty-format";
import RecoveryStore from "./fs/RecoveryStore";
@@ -167,6 +170,21 @@ function validateAllowedRequestFlag(
}
}
+function sendLog(
+ bridge: BridgeServer<typeof ServerBridge>,
+ level: ClientLogsLevel,
+ log: ServerBridgeLog,
+) {
+ // Sometimes the bridge hasn't completely been teardown and we still consider it connected
+ if (!bridge.alive) {
+ return;
+ }
+
+ if (log.isError || level === "all") {
+ bridge.events.log.send(log);
+ }
+}
+
export default class Server {
constructor(opts: ServerOptions) {
this.profiling = undefined;
@@ -191,13 +209,14 @@ export default class Server {
this.connectedReporters = new ServerReporter(this);
- this.connectedClientsListeningForLogs = new Set();
+ this.connectedClientsListeningForWorkerLogs = new Set();
+ this.connectedClientsListeningForLogs = new Map();
this.connectedLSPServers = new Set();
this.connectedClients = new Set();
this.clientIdCounter = 0;
- this.logInitBuffer = "";
+ this.logInitBuffer = [];
this.requestRunningCounter = 0;
this.terminateWhenIdle = false;
@@ -242,20 +261,27 @@ export default class Server {
},
},
},
+ "server",
+ );
+
+ this.loggerStream = this.logger.attachConditionalStream(
{
- loggerType: "server",
- write: (chunk) => {
- this.emitServerLog(chunk);
+ format: "markup",
+ features: {
+ ...DEFAULT_TERMINAL_FEATURES,
+ columns: undefined,
},
- check: () => {
+ write: (chunk, isError) => {
+ this.emitLog(chunk, "server", isError);
+ },
+ },
+ () => {
return (
this.clientIdCounter === 0 ||
this.connectedClientsListeningForLogs.size > 0
);
},
- },
);
- this.logger.updateStream();
this.virtualModules = new VirtualModules();
this.recoveryStore = new RecoveryStore(this);
@@ -300,9 +326,11 @@ export default class Server {
public logger: Logger;
public requestFileLocker: FilePathLocker;
+ private loggerStream: ReporterConditionalStream;
+
// Before we receive our first connected client we will buffer our server init logs
- // These should be relatively cheap to process since we don't do a lot
- private logInitBuffer: string;
+ // These __should__ be relatively cheap to retain since we don't do a lot
+ private logInitBuffer: [string, boolean][];
private requestRunningCounter: number;
private terminateWhenIdle: boolean;
@@ -311,7 +339,9 @@ export default class Server {
private connectedClients: Set<ServerClient>;
private connectedLSPServers: Set<LSPServer>;
- private connectedClientsListeningForLogs: Set<ServerClient>;
+
+ private connectedClientsListeningForLogs: Map<ServerClient, ClientLogsLevel>;
+ private connectedClientsListeningForWorkerLogs: Set<ServerClient>;
// Used when starting up child processes and indicates whether they should start profiling
// on init
@@ -319,6 +349,21 @@ export default class Server {
return this.profiling;
}
+ // Used when starting up workers and indicates whether they should start sending logs
+ public hasWorkerLogsSubscriptions(): boolean {
+ return this.connectedClientsListeningForWorkerLogs.size > 0;
+ }
+
+ public async updateWorkerLogsSubscriptions() {
+ const enabled = this.hasWorkerLogsSubscriptions();
+
+ await Promise.all(
+ this.workerManager.getWorkers().map((worker) => {
+ return worker.bridge.events.setLogs.call(enabled);
+ }),
+ );
+ }
+
// Derive a concatenated reporter from the logger and all connected clients
// This should only be used synchronously as the streams will not stay in sync
// Used for very important log messages
@@ -332,16 +377,26 @@ export default class Server {
return new WorkerQueue<M>(this, opts);
}
- private emitServerLog(chunk: string) {
+ public emitLog(
+ chunk: string,
+ origin: ServerBridgeLog["origin"],
+ isError: boolean,
+ ) {
if (this.clientIdCounter === 0) {
- this.logInitBuffer += chunk;
+ this.logInitBuffer.push([chunk, isError]);
}
- for (const {bridge} of this.connectedClientsListeningForLogs) {
- // Sometimes the bridge hasn't completely been teardown and we still consider it connected
- if (bridge.alive) {
- bridge.events.log.send({chunk, origin: "server"});
+ const log: ServerBridgeLog = {chunk, origin, isError};
+
+ for (const [client, level] of this.connectedClientsListeningForLogs) {
+ if (
+ origin === "worker" &&
+ !this.connectedClientsListeningForWorkerLogs.has(client)
+ ) {
+ continue;
}
+
+ sendLog(client.bridge, level, log);
}
}
@@ -536,34 +591,6 @@ export default class Server {
return await worker.bridge.events.profilingStop.call();
});
- // When enableWorkerLogs is called we setup subscriptions to the worker logs
- // Logs are never transported from workers to the server unless there is a subscription
- let subscribedWorkers = false;
- bridge.events.enableWorkerLogs.subscribe(() => {
- // enableWorkerLogs could be called twice in the case of `--logs --rage`. We'll only want to setup the subscriptions once
- if (subscribedWorkers) {
- return;
- } else {
- subscribedWorkers = true;
- }
-
- function onLog(chunk: string) {
- bridge.events.log.call({origin: "worker", chunk});
- }
-
- // Add on existing workers if there are any
- for (const worker of this.workerManager.getWorkers()) {
- bridge.attachEndSubscriptionRemoval(
- worker.bridge.events.log.subscribe(onLog),
- );
- }
-
- // Listen for logs for any workers that start later
- this.workerManager.workerStartEvent.subscribe((worker) => {
- bridge.attachEndSubscriptionRemoval(worker.events.log.subscribe(onLog));
- });
- });
-
await bridge.handshake();
const client = await this.createClient(bridge);
@@ -662,31 +689,50 @@ export default class Server {
this.connectedClients.add(client);
- bridge.updatedListenersEvent.subscribe((listeners) => {
- if (listeners.has("log")) {
- if (!this.connectedClientsListeningForLogs.has(client)) {
- this.connectedClientsListeningForLogs.add(client);
- let buffer = this.logInitBuffer;
- buffer += ".".repeat(20);
- buffer += "\n";
- bridge.events.log.send({
- chunk: buffer,
- origin: "server",
- });
+ bridge.events.setLogLevel.subscribe(async ({level, includeWorker}) => {
+ let startWorkerLogsEnabled = this.hasWorkerLogsSubscriptions();
+
+ if (includeWorker) {
+ this.connectedClientsListeningForWorkerLogs.add(client);
}
- } else {
+
+ if (level === undefined) {
this.connectedClientsListeningForLogs.delete(client);
+ this.connectedClientsListeningForWorkerLogs.delete(client);
+ } else {
+ if (!this.connectedClientsListeningForLogs.has(client)) {
+ // Send init logs
+ for (const [chunk, isError] of this.logInitBuffer) {
+ sendLog(bridge, level, {chunk, origin: "server", isError});
+ }
+
+ // Send separator
+ sendLog(
+ bridge,
+ level,
+ {chunk: ".".repeat(20) + "\n", origin: "server", isError: false},
+ );
+ }
+
+ this.connectedClientsListeningForLogs.set(client, level);
+ }
+
+ this.loggerStream.update();
+
+ const currWorkerLogsEnabled = this.hasWorkerLogsSubscriptions();
+ if (currWorkerLogsEnabled !== startWorkerLogsEnabled) {
+ await this.updateWorkerLogsSubscriptions();
}
- this.logger.updateStream();
});
bridge.endEvent.subscribe(() => {
this.connectedClients.delete(client);
this.connectedClientsListeningForLogs.delete(client);
+ this.connectedClientsListeningForWorkerLogs.delete(client);
for (const handle of streamHandles) {
handle.remove();
}
- this.logger.updateStream();
+ this.loggerStream.update();
// Cancel any requests still in flight
for (const req of client.requestsInFlight) {

MODIFY
internal/core/server/ServerCache.ts
internal/core/server/ServerCache.ts
@@ -65,6 +65,7 @@ export default class ServerCache extends Cache {
if (this.readDisabled) {
logger.warn(markup`Read disabled, skipping breaker verification`);
+ return;
}
if (await exists(breakerPath)) {

MODIFY
internal/core/server/ServerRequest.ts
internal/core/server/ServerRequest.ts
@@ -239,7 +239,8 @@ export default class ServerRequest {
this.files = new AbsoluteFilePathMap();
this.logger = server.logger.namespace(
- markup`[ServerRequest] Request #${this.id}:`,
+ markup`ServerRequest`,
+ markup`Request #${this.id}`,
);
this.markerEvent = new Event({

MODIFY
internal/core/server/WorkerManager.ts
internal/core/server/WorkerManager.ts
@@ -56,7 +56,7 @@ export default class WorkerManager {
this.logger = server.logger.namespace(markup`WorkerManager`);
}
- public workerStartEvent: Event<BridgeServer<typeof WorkerBridge>, void>;
+ public workerStartEvent: Event<WorkerContainer, void>;
public locker: Locker<number>;
private server: Server;
@@ -175,7 +175,7 @@ export default class WorkerManager {
bridges.client.handshake(),
]);
- this.workerStartEvent.send(bridges.server);
+ this.workerStartEvent.send(container);
}
private async replaceOwnWorker() {
@@ -293,6 +293,10 @@ export default class WorkerManager {
);
});
+ bridge.events.log.subscribe(({chunk, isError}) => {
+ this.server.emitLog(chunk, "worker", isError);
+ });
+
bridge.monitorHeartbeat(
LAG_INTERVAL,
({summary, totalTime, iterations}) => {
@@ -318,7 +322,7 @@ export default class WorkerManager {
},
);
- const worker: WorkerContainer = {
+ const container: WorkerContainer = {
id: workerId,
fileCount: 0,
byteCount: 0n,
@@ -327,7 +331,7 @@ export default class WorkerManager {
ghost: isGhost,
ready: false,
};
- this.workers.set(workerId, worker);
+ this.workers.set(workerId, container);
thread.once(
"error",
@@ -340,7 +344,7 @@ export default class WorkerManager {
},
);
- await this.workerHandshake(worker);
+ await this.workerHandshake(container);
// If a worker is spawned while we're profiling then make sure it's profiling too
const profileData = this.server.getRunningProfilingData();
@@ -348,7 +352,11 @@ export default class WorkerManager {
await bridge.events.profilingStart.call(profileData);
}
- this.workerStartEvent.send(bridge);
+ if (this.server.hasWorkerLogsSubscriptions()) {
+ await bridge.events.setLogs.call(true);
+ }
+
+ this.workerStartEvent.send(container);
this.logger.info(
markup`Worker ${String(workerId)} started after <duration>${String(
@@ -356,7 +364,7 @@ export default class WorkerManager {
)}</duration>`,
);
- return worker;
+ return container;
}
public own(workerId: number, stats: SimpleStats) {

MODIFY
internal/core/worker/Worker.ts
internal/core/worker/Worker.ts
@@ -44,6 +44,8 @@ import {ExtendedMap} from "@internal/collections";
import WorkerCache from "./WorkerCache";
import FatalErrorHandler from "../common/FatalErrorHandler";
import {RSERObject} from "@internal/codec-binary-serial";
+import {ReporterConditionalStream} from "@internal/cli-reporter";
+import {DEFAULT_TERMINAL_FEATURES} from "@internal/cli-environment";
export type ParseResult = {
ast: AnyRoot;
@@ -83,18 +85,17 @@ export default class Worker {
this.buffers = new AbsoluteFilePathMap();
this.virtualModules = new VirtualModules();
- this.logger = new Logger(
- {},
- {
- loggerType: "worker",
- check: () => opts.bridge.events.log.hasSubscribers(),
- write(chunk) {
- opts.bridge.events.log.send(chunk.toString());
+ this.logger = new Logger({}, "worker");
+
+ this.loggerStream = this.logger.attachConditionalStream({
+ format: "markup",
+ features: {
+ ...DEFAULT_TERMINAL_FEATURES,
+ columns: undefined,
},
+ write(chunk, isError) {
+ opts.bridge.events.log.send({chunk: chunk.toString(), isError});
},
- );
- opts.bridge.updatedListenersEvent.subscribe(() => {
- this.logger.updateStream();
});
this.cache = new WorkerCache(this);
@@ -143,6 +144,7 @@ export default class Worker {
public fatalErrorHandler: FatalErrorHandler;
public virtualModules: VirtualModules;
+ private loggerStream: ReporterConditionalStream;
private cache: WorkerCache;
private bridge: BridgeClient<typeof WorkerBridge>;
private partialManifests: ExtendedMap<number, WorkerPartialManifest>;
@@ -236,6 +238,14 @@ export default class Worker {
return this.updateProjects(payload.projects);
});
+ bridge.events.setLogs.subscribe((enabled) => {
+ if (enabled) {
+ this.loggerStream.enable();
+ } else {
+ this.loggerStream.disable();
+ }
+ });
+
bridge.events.updateManifests.subscribe((payload) => {
return this.updateManifests(payload.manifests);
});

MODIFY
internal/events/utils.ts
internal/events/utils.ts
@@ -10,6 +10,10 @@ import {EventSubscription, EventSubscriptions} from "./types";
export function mergeEventSubscriptions(
subs: EventSubscriptions,
): EventSubscription {
+ if (subs.length === 1) {
+ return subs[0];
+ }
+
return {
async unsubscribe() {
for (const sub of subs) {

MODIFY
internal/test-helpers/integration.ts
internal/test-helpers/integration.ts
@@ -418,6 +418,7 @@ export function createIntegrationTest(
// Capture client logs
let logs = "";
await client.subscribeLogs(
+ "all",
true,
(chunk) => {
const textChunk = joinMarkupLines(

MODIFY
website/src/_includes/docs/cli.md
website/src/_includes/docs/cli.md
@@ -54,10 +54,14 @@ This is useful as it will benchmark the command after server initialization and
The amount of iterations to perform when using the `--benchmark` flag. Defaults to `10`.
-##### `--logs`
+##### `--logs all`
Enables server logs and outputs them to the console.
+##### `--logs error`
+
+Exclude info logs and only include warnings and errors.
+
##### `--log-workers`
Enables worker logs, by default these are not output when running `--logs`.

Add `--logs all` and `--logs error` (#1333)