v3: implement configurable log levels via config file and env var (#985)
* v3: implement configurable log levels via config file and TRIGGER_LOG_LEVEL Also, test runs automatically set the TRIGGER_LOG_LEVEL to debug * Fix type error and changeset * Added a Node.js runtime check for the CLI dev command
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@trigger.dev/sdk": patch
|
||||
"trigger.dev": patch
|
||||
"@trigger.dev/core": patch
|
||||
---
|
||||
|
||||
Configurable log levels in the config file and via env var
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"trigger.dev": patch
|
||||
---
|
||||
|
||||
Added a Node.js runtime check for the CLI
|
||||
@@ -21,15 +21,15 @@ export function commonOptions(command: Command) {
|
||||
.option("-a, --api-url <value>", "Override the API URL", "https://api.trigger.dev")
|
||||
.option(
|
||||
"-l, --log-level <level>",
|
||||
"The log level to use (debug, info, log, warn, error, none)",
|
||||
"The CLI log level to use (debug, info, log, warn, error, none). This does not effect the log level of your trigger.dev tasks.",
|
||||
"log"
|
||||
)
|
||||
.option("--skip-telemetry", "Opt-out of sending telemetry");
|
||||
}
|
||||
|
||||
export class SkipLoggingError extends Error { }
|
||||
export class SkipCommandError extends Error { }
|
||||
export class OutroCommandError extends SkipCommandError { }
|
||||
export class SkipLoggingError extends Error {}
|
||||
export class SkipCommandError extends Error {}
|
||||
export class OutroCommandError extends SkipCommandError {}
|
||||
|
||||
export async function handleTelemetry(action: () => Promise<void>) {
|
||||
try {
|
||||
|
||||
@@ -40,6 +40,7 @@ import { isLoggedIn } from "../utilities/session.js";
|
||||
import { createTaskFileImports, gatherTaskFiles } from "../utilities/taskFiles";
|
||||
import { UncaughtExceptionError } from "../workers/common/errors";
|
||||
import { BackgroundWorker, BackgroundWorkerCoordinator } from "../workers/dev/backgroundWorker.js";
|
||||
import { runtimeCheck } from "../utilities/runtimeCheck";
|
||||
|
||||
let apiClient: CliApiClient | undefined;
|
||||
|
||||
@@ -72,7 +73,18 @@ export function configureDevCommand(program: Command) {
|
||||
});
|
||||
}
|
||||
|
||||
const MINIMUM_NODE_MAJOR = 18;
|
||||
const MINIMUM_NODE_MINOR = 16;
|
||||
|
||||
export async function devCommand(dir: string, options: DevCommandOptions) {
|
||||
try {
|
||||
runtimeCheck(MINIMUM_NODE_MAJOR, MINIMUM_NODE_MINOR);
|
||||
} catch (e) {
|
||||
logger.log(`${chalkError("X Error:")} ${e}`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
const authorization = await isLoggedIn(options.profile);
|
||||
|
||||
if (!authorization.ok) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { TriggerConfig } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export const config: TriggerConfig = {
|
||||
project: "${projectRef}",
|
||||
logLevel: "log",
|
||||
retries: {
|
||||
enabledInDev: false,
|
||||
default: {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { logger } from "./logger";
|
||||
|
||||
/**
|
||||
* This function is used by the dev CLI to make sure that the runtime is compatible
|
||||
*/
|
||||
export function runtimeCheck(minimumMajor: number, minimumMinor: number) {
|
||||
// Check if the runtime is Node.js
|
||||
if (typeof process === "undefined") {
|
||||
throw "The dev CLI can only be run in a Node.js compatible environment";
|
||||
}
|
||||
|
||||
// Check if the runtime version is compatible
|
||||
const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
|
||||
|
||||
const isBun = typeof process.versions.bun === "string";
|
||||
|
||||
if (major < minimumMajor || (major === minimumMajor && minor < minimumMinor)) {
|
||||
if (isBun) {
|
||||
throw `The dev CLI requires at least Node.js ${minimumMajor}.${minimumMinor}. You are running Bun ${process.versions.bun}, which is compatible with Node.js ${process.versions.node}`;
|
||||
} else {
|
||||
throw `The dev CLI requires at least Node.js ${minimumMajor}.${minimumMinor}. You are running Node.js ${process.versions.node}`;
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Node.js version: ${process.versions.node}${isBun ? ` (Bun ${process.versions.bun})` : ""}`
|
||||
);
|
||||
}
|
||||
@@ -385,7 +385,7 @@ export class BackgroundWorker {
|
||||
|
||||
if (!this._taskRunProcesses.has(payload.execution.run.id)) {
|
||||
const taskRunProcess = new TaskRunProcess(
|
||||
payload.execution.run.id,
|
||||
payload.execution,
|
||||
this.path,
|
||||
{
|
||||
...this.params.env,
|
||||
@@ -542,7 +542,7 @@ class TaskRunProcess {
|
||||
public onExit: Evt<number> = new Evt();
|
||||
|
||||
constructor(
|
||||
private runId: string,
|
||||
private execution: TaskRunExecution,
|
||||
private path: string,
|
||||
private env: NodeJS.ProcessEnv,
|
||||
private metadata: BackgroundWorkerProperties,
|
||||
@@ -565,22 +565,25 @@ class TaskRunProcess {
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
logger.debug(`[${this.runId}] initializing task run process`, {
|
||||
env: this.env,
|
||||
const fullEnv = {
|
||||
...(this.execution.run.isTest ? { TRIGGER_LOG_LEVEL: "debug" } : {}),
|
||||
...this.env,
|
||||
OTEL_RESOURCE_ATTRIBUTES: JSON.stringify({
|
||||
[SemanticInternalAttributes.PROJECT_DIR]: this.worker.projectConfig.projectDir,
|
||||
}),
|
||||
OTEL_EXPORTER_OTLP_COMPRESSION: "none",
|
||||
...(this.worker.debugOtel ? { OTEL_LOG_LEVEL: "debug" } : {}),
|
||||
};
|
||||
|
||||
logger.debug(`[${this.execution.run.id}] initializing task run process`, {
|
||||
env: fullEnv,
|
||||
path: this.path,
|
||||
});
|
||||
|
||||
this._child = fork(this.path, {
|
||||
stdio: [/*stdin*/ "ignore", /*stdout*/ "pipe", /*stderr*/ "pipe", "ipc"],
|
||||
cwd: dirname(this.path),
|
||||
env: {
|
||||
...this.env,
|
||||
OTEL_RESOURCE_ATTRIBUTES: JSON.stringify({
|
||||
[SemanticInternalAttributes.PROJECT_DIR]: this.worker.projectConfig.projectDir,
|
||||
}),
|
||||
OTEL_EXPORTER_OTLP_COMPRESSION: "none",
|
||||
...(this.worker.debugOtel ? { OTEL_LOG_LEVEL: "debug" } : {}),
|
||||
},
|
||||
env: fullEnv,
|
||||
execArgv: this.worker.debuggerOn
|
||||
? ["--inspect-brk", "--trace-uncaught", "--no-warnings=ExperimentalWarning"]
|
||||
: ["--trace-uncaught", "--no-warnings=ExperimentalWarning"],
|
||||
@@ -597,7 +600,7 @@ class TaskRunProcess {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`[${this.runId}] cleaning up task run process`, { kill });
|
||||
logger.debug(`[${this.execution.run.id}] cleaning up task run process`, { kill });
|
||||
|
||||
await this._sender.send("CLEANUP", {
|
||||
flush: true,
|
||||
@@ -643,12 +646,15 @@ class TaskRunProcess {
|
||||
return;
|
||||
}
|
||||
|
||||
if (execution.run.id === this.runId) {
|
||||
if (execution.run.id === this.execution.run.id) {
|
||||
// We don't need to notify the task run process if it's the same as the one we're running
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`[${this.runId}] task run completed notification`, { completion, execution });
|
||||
logger.debug(`[${this.execution.run.id}] task run completed notification`, {
|
||||
completion,
|
||||
execution,
|
||||
});
|
||||
|
||||
this._sender.send("TASK_RUN_COMPLETED_NOTIFICATION", {
|
||||
completion,
|
||||
@@ -700,7 +706,7 @@ class TaskRunProcess {
|
||||
}
|
||||
|
||||
async #handleExit(code: number) {
|
||||
logger.debug(`[${this.runId}] task run process exiting`, { code });
|
||||
logger.debug(`[${this.execution.run.id}] task run process exiting`, { code });
|
||||
|
||||
// Go through all the attempts currently pending and reject them
|
||||
for (const [id, status] of this._attemptStatuses.entries()) {
|
||||
|
||||
@@ -6,6 +6,9 @@ import {
|
||||
type HandleErrorFunction,
|
||||
DurableClock,
|
||||
clock,
|
||||
logLevels,
|
||||
LogLevel,
|
||||
getEnvVar,
|
||||
} from "@trigger.dev/core/v3";
|
||||
|
||||
__WORKER_SETUP__;
|
||||
@@ -53,10 +56,18 @@ const devRuntimeManager = new DevRuntimeManager();
|
||||
|
||||
runtime.setGlobalRuntimeManager(devRuntimeManager);
|
||||
|
||||
const triggerLogLevel = getEnvVar("TRIGGER_LOG_LEVEL");
|
||||
|
||||
const configLogLevel = triggerLogLevel
|
||||
? triggerLogLevel
|
||||
: importedConfig
|
||||
? importedConfig.logLevel
|
||||
: __PROJECT_CONFIG__.logLevel;
|
||||
|
||||
const otelTaskLogger = new OtelTaskLogger({
|
||||
logger: otelLogger,
|
||||
tracer: tracer,
|
||||
level: "info",
|
||||
level: logLevels.includes(configLogLevel as any) ? (configLogLevel as LogLevel) : "log",
|
||||
});
|
||||
|
||||
logger.setGlobalTaskLogger(otelTaskLogger);
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
CreateBackgroundWorkerResponse,
|
||||
InferSocketMessageSchema,
|
||||
ProdChildToWorkerMessages,
|
||||
ProdTaskRunExecution,
|
||||
ProdTaskRunExecutionPayload,
|
||||
ProdWorkerToChildMessages,
|
||||
SemanticInternalAttributes,
|
||||
@@ -200,6 +201,7 @@ export class ProdBackgroundWorker {
|
||||
|
||||
if (!this._taskRunProcess) {
|
||||
const taskRunProcess = new TaskRunProcess(
|
||||
payload.execution,
|
||||
this.path,
|
||||
{
|
||||
...this.params.env,
|
||||
@@ -374,6 +376,7 @@ class TaskRunProcess {
|
||||
public onCancelCheckpoint = Evt.create<{ version?: "v1" }>();
|
||||
|
||||
constructor(
|
||||
private execution: ProdTaskRunExecution,
|
||||
private path: string,
|
||||
private env: NodeJS.ProcessEnv,
|
||||
private metadata: BackgroundWorkerProperties,
|
||||
@@ -384,6 +387,7 @@ class TaskRunProcess {
|
||||
this._child = fork(this.path, {
|
||||
stdio: [/*stdin*/ "ignore", /*stdout*/ "pipe", /*stderr*/ "pipe", "ipc"],
|
||||
env: {
|
||||
...(this.execution.run.isTest ? { TRIGGER_LOG_LEVEL: "debug" } : {}),
|
||||
...this.env,
|
||||
OTEL_RESOURCE_ATTRIBUTES: JSON.stringify({
|
||||
[SemanticInternalAttributes.PROJECT_DIR]: this.worker.projectConfig.projectDir,
|
||||
|
||||
@@ -9,6 +9,9 @@ import {
|
||||
HandleErrorFunction,
|
||||
DurableClock,
|
||||
clock,
|
||||
getEnvVar,
|
||||
logLevels,
|
||||
LogLevel,
|
||||
} from "@trigger.dev/core/v3";
|
||||
import "source-map-support/register.js";
|
||||
|
||||
@@ -47,10 +50,18 @@ clock.setGlobalClock(durableClock);
|
||||
const tracer = new TriggerTracer({ tracer: otelTracer, logger: otelLogger });
|
||||
const consoleInterceptor = new ConsoleInterceptor(otelLogger);
|
||||
|
||||
const triggerLogLevel = getEnvVar("TRIGGER_LOG_LEVEL");
|
||||
|
||||
const configLogLevel = triggerLogLevel
|
||||
? triggerLogLevel
|
||||
: importedConfig
|
||||
? importedConfig.logLevel
|
||||
: __PROJECT_CONFIG__.logLevel;
|
||||
|
||||
const otelTaskLogger = new OtelTaskLogger({
|
||||
logger: otelLogger,
|
||||
tracer: tracer,
|
||||
level: "info",
|
||||
level: logLevels.includes(configLogLevel as any) ? (configLogLevel as LogLevel) : "log",
|
||||
});
|
||||
|
||||
logger.setGlobalTaskLogger(otelTaskLogger);
|
||||
|
||||
@@ -39,8 +39,8 @@ export { ProdRuntimeManager } from "./runtime/prodRuntimeManager";
|
||||
export { PreciseWallClock as DurableClock } from "./clock/preciseWallClock";
|
||||
export { TriggerTracer } from "./tracer";
|
||||
|
||||
export type { TaskLogger } from "./logger/taskLogger";
|
||||
export { OtelTaskLogger } from "./logger/taskLogger";
|
||||
export type { TaskLogger, LogLevel } from "./logger/taskLogger";
|
||||
export { OtelTaskLogger, logLevels } from "./logger/taskLogger";
|
||||
export { ConsoleInterceptor } from "./consoleInterceptor";
|
||||
export {
|
||||
flattenAttributes,
|
||||
|
||||
@@ -7,9 +7,9 @@ import { flattenAttributes } from "../utils/flattenAttributes";
|
||||
import { ClockTime } from "../clock/clock";
|
||||
import { clock } from "../clock-api";
|
||||
|
||||
export type LogLevel = "log" | "error" | "warn" | "info" | "debug";
|
||||
export type LogLevel = "none" | "log" | "error" | "warn" | "info" | "debug";
|
||||
|
||||
const logLevels: Array<LogLevel> = ["error", "warn", "log", "info", "debug"];
|
||||
export const logLevels: Array<LogLevel> = ["none", "error", "warn", "log", "info", "debug"];
|
||||
|
||||
export type TaskLoggerConfig = {
|
||||
logger: Logger;
|
||||
@@ -34,31 +34,31 @@ export class OtelTaskLogger implements TaskLogger {
|
||||
}
|
||||
|
||||
debug(message: string, properties?: Record<string, unknown>) {
|
||||
if (this._level < 4) return;
|
||||
if (this._level < 5) return;
|
||||
|
||||
this.#emitLog(message, this.#getTimestampInHrTime(), "debug", SeverityNumber.DEBUG, properties);
|
||||
}
|
||||
|
||||
log(message: string, properties?: Record<string, unknown>) {
|
||||
if (this._level < 2) return;
|
||||
if (this._level < 3) return;
|
||||
|
||||
this.#emitLog(message, this.#getTimestampInHrTime(), "log", SeverityNumber.INFO, properties);
|
||||
}
|
||||
|
||||
info(message: string, properties?: Record<string, unknown>) {
|
||||
if (this._level < 3) return;
|
||||
if (this._level < 4) return;
|
||||
|
||||
this.#emitLog(message, this.#getTimestampInHrTime(), "info", SeverityNumber.INFO, properties);
|
||||
}
|
||||
|
||||
warn(message: string, properties?: Record<string, unknown>) {
|
||||
if (this._level < 1) return;
|
||||
if (this._level < 2) return;
|
||||
|
||||
this.#emitLog(message, this.#getTimestampInHrTime(), "warn", SeverityNumber.WARN, properties);
|
||||
}
|
||||
|
||||
error(message: string, properties?: Record<string, unknown>) {
|
||||
if (this._level < 0) return;
|
||||
if (this._level < 1) return;
|
||||
|
||||
this.#emitLog(message, this.#getTimestampInHrTime(), "error", SeverityNumber.ERROR, properties);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ export const Config = z.object({
|
||||
additionalPackages: z.string().array().optional(),
|
||||
additionalFiles: z.string().array().optional(),
|
||||
dependenciesToBundle: z.array(z.union([z.string(), RegexSchema])).optional(),
|
||||
logLevel: z.string().optional(),
|
||||
});
|
||||
|
||||
export type Config = z.infer<typeof Config>;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LogLevel } from "../logger/taskLogger";
|
||||
import { RetryOptions } from "../schemas";
|
||||
import type { InstrumentationOption } from "@opentelemetry/instrumentation";
|
||||
|
||||
@@ -31,4 +32,13 @@ export interface ProjectConfig {
|
||||
* The OpenTelemetry instrumentations to enable
|
||||
*/
|
||||
instrumentations?: InstrumentationOption[];
|
||||
|
||||
/**
|
||||
* Set the log level for the logger. Defaults to "log", so you will see "log", "warn", and "error" messages, but not "info", or "debug" messages.
|
||||
*
|
||||
* We automatically set the logLevel to "debug" during test runs
|
||||
*
|
||||
* @default "log"
|
||||
*/
|
||||
logLevel?: LogLevel;
|
||||
}
|
||||
|
||||
@@ -7,4 +7,4 @@ export { retry, type RetryOptions } from "./retry";
|
||||
import type { Context } from "./shared";
|
||||
export type { Context };
|
||||
|
||||
export { logger } from "@trigger.dev/core/v3";
|
||||
export { logger, type LogLevel } from "@trigger.dev/core/v3";
|
||||
|
||||
@@ -4,9 +4,11 @@ import slugify from "@sindresorhus/slugify";
|
||||
export const loggingTask = task({
|
||||
id: "logging-task",
|
||||
run: async () => {
|
||||
console.log(`Hello world 9 ${slugify("foo bar")}`);
|
||||
|
||||
return null;
|
||||
logger.error("This is an error message");
|
||||
logger.warn("This is a warning message");
|
||||
logger.log("This is a log message");
|
||||
logger.info("This is an info message");
|
||||
logger.debug("This is a debug message");
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -19,4 +19,5 @@ export const config: TriggerConfig = {
|
||||
additionalFiles: ["./wrangler/wrangler.toml"],
|
||||
dependenciesToBundle: [/@sindresorhus/, "escape-string-regexp"],
|
||||
instrumentations: [new OpenAIInstrumentation()],
|
||||
logLevel: "log",
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user