Skip to content

Commit b271742

Browse files
authored
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
1 parent 6f9f254 commit b271742

File tree

17 files changed

+134
-35
lines changed

17 files changed

+134
-35
lines changed

.changeset/cool-glasses-bake.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
"trigger.dev": patch
4+
"@trigger.dev/core": patch
5+
---
6+
7+
Configurable log levels in the config file and via env var

.changeset/odd-poets-own.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trigger.dev": patch
3+
---
4+
5+
Added a Node.js runtime check for the CLI

packages/cli-v3/src/cli/common.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ export function commonOptions(command: Command) {
2121
.option("-a, --api-url <value>", "Override the API URL", "https://api.trigger.dev")
2222
.option(
2323
"-l, --log-level <level>",
24-
"The log level to use (debug, info, log, warn, error, none)",
24+
"The CLI log level to use (debug, info, log, warn, error, none). This does not effect the log level of your trigger.dev tasks.",
2525
"log"
2626
)
2727
.option("--skip-telemetry", "Opt-out of sending telemetry");
2828
}
2929

30-
export class SkipLoggingError extends Error { }
31-
export class SkipCommandError extends Error { }
32-
export class OutroCommandError extends SkipCommandError { }
30+
export class SkipLoggingError extends Error {}
31+
export class SkipCommandError extends Error {}
32+
export class OutroCommandError extends SkipCommandError {}
3333

3434
export async function handleTelemetry(action: () => Promise<void>) {
3535
try {

packages/cli-v3/src/commands/dev.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { isLoggedIn } from "../utilities/session.js";
4040
import { createTaskFileImports, gatherTaskFiles } from "../utilities/taskFiles";
4141
import { UncaughtExceptionError } from "../workers/common/errors";
4242
import { BackgroundWorker, BackgroundWorkerCoordinator } from "../workers/dev/backgroundWorker.js";
43+
import { runtimeCheck } from "../utilities/runtimeCheck";
4344

4445
let apiClient: CliApiClient | undefined;
4546

@@ -72,7 +73,18 @@ export function configureDevCommand(program: Command) {
7273
});
7374
}
7475

76+
const MINIMUM_NODE_MAJOR = 18;
77+
const MINIMUM_NODE_MINOR = 16;
78+
7579
export async function devCommand(dir: string, options: DevCommandOptions) {
80+
try {
81+
runtimeCheck(MINIMUM_NODE_MAJOR, MINIMUM_NODE_MINOR);
82+
} catch (e) {
83+
logger.log(`${chalkError("X Error:")} ${e}`);
84+
process.exitCode = 1;
85+
return;
86+
}
87+
7688
const authorization = await isLoggedIn(options.profile);
7789

7890
if (!authorization.ok) {

packages/cli-v3/src/templates/trigger.config.ts.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { TriggerConfig } from "@trigger.dev/sdk/v3";
22

33
export const config: TriggerConfig = {
44
project: "${projectRef}",
5+
logLevel: "log",
56
retries: {
67
enabledInDev: false,
78
default: {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { logger } from "./logger";
2+
3+
/**
4+
* This function is used by the dev CLI to make sure that the runtime is compatible
5+
*/
6+
export function runtimeCheck(minimumMajor: number, minimumMinor: number) {
7+
// Check if the runtime is Node.js
8+
if (typeof process === "undefined") {
9+
throw "The dev CLI can only be run in a Node.js compatible environment";
10+
}
11+
12+
// Check if the runtime version is compatible
13+
const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
14+
15+
const isBun = typeof process.versions.bun === "string";
16+
17+
if (major < minimumMajor || (major === minimumMajor && minor < minimumMinor)) {
18+
if (isBun) {
19+
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}`;
20+
} else {
21+
throw `The dev CLI requires at least Node.js ${minimumMajor}.${minimumMinor}. You are running Node.js ${process.versions.node}`;
22+
}
23+
}
24+
25+
logger.debug(
26+
`Node.js version: ${process.versions.node}${isBun ? ` (Bun ${process.versions.bun})` : ""}`
27+
);
28+
}

packages/cli-v3/src/workers/dev/backgroundWorker.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ export class BackgroundWorker {
385385

386386
if (!this._taskRunProcesses.has(payload.execution.run.id)) {
387387
const taskRunProcess = new TaskRunProcess(
388-
payload.execution.run.id,
388+
payload.execution,
389389
this.path,
390390
{
391391
...this.params.env,
@@ -542,7 +542,7 @@ class TaskRunProcess {
542542
public onExit: Evt<number> = new Evt();
543543

544544
constructor(
545-
private runId: string,
545+
private execution: TaskRunExecution,
546546
private path: string,
547547
private env: NodeJS.ProcessEnv,
548548
private metadata: BackgroundWorkerProperties,
@@ -565,22 +565,25 @@ class TaskRunProcess {
565565
}
566566

567567
async initialize() {
568-
logger.debug(`[${this.runId}] initializing task run process`, {
569-
env: this.env,
568+
const fullEnv = {
569+
...(this.execution.run.isTest ? { TRIGGER_LOG_LEVEL: "debug" } : {}),
570+
...this.env,
571+
OTEL_RESOURCE_ATTRIBUTES: JSON.stringify({
572+
[SemanticInternalAttributes.PROJECT_DIR]: this.worker.projectConfig.projectDir,
573+
}),
574+
OTEL_EXPORTER_OTLP_COMPRESSION: "none",
575+
...(this.worker.debugOtel ? { OTEL_LOG_LEVEL: "debug" } : {}),
576+
};
577+
578+
logger.debug(`[${this.execution.run.id}] initializing task run process`, {
579+
env: fullEnv,
570580
path: this.path,
571581
});
572582

573583
this._child = fork(this.path, {
574584
stdio: [/*stdin*/ "ignore", /*stdout*/ "pipe", /*stderr*/ "pipe", "ipc"],
575585
cwd: dirname(this.path),
576-
env: {
577-
...this.env,
578-
OTEL_RESOURCE_ATTRIBUTES: JSON.stringify({
579-
[SemanticInternalAttributes.PROJECT_DIR]: this.worker.projectConfig.projectDir,
580-
}),
581-
OTEL_EXPORTER_OTLP_COMPRESSION: "none",
582-
...(this.worker.debugOtel ? { OTEL_LOG_LEVEL: "debug" } : {}),
583-
},
586+
env: fullEnv,
584587
execArgv: this.worker.debuggerOn
585588
? ["--inspect-brk", "--trace-uncaught", "--no-warnings=ExperimentalWarning"]
586589
: ["--trace-uncaught", "--no-warnings=ExperimentalWarning"],
@@ -597,7 +600,7 @@ class TaskRunProcess {
597600
return;
598601
}
599602

600-
logger.debug(`[${this.runId}] cleaning up task run process`, { kill });
603+
logger.debug(`[${this.execution.run.id}] cleaning up task run process`, { kill });
601604

602605
await this._sender.send("CLEANUP", {
603606
flush: true,
@@ -643,12 +646,15 @@ class TaskRunProcess {
643646
return;
644647
}
645648

646-
if (execution.run.id === this.runId) {
649+
if (execution.run.id === this.execution.run.id) {
647650
// We don't need to notify the task run process if it's the same as the one we're running
648651
return;
649652
}
650653

651-
logger.debug(`[${this.runId}] task run completed notification`, { completion, execution });
654+
logger.debug(`[${this.execution.run.id}] task run completed notification`, {
655+
completion,
656+
execution,
657+
});
652658

653659
this._sender.send("TASK_RUN_COMPLETED_NOTIFICATION", {
654660
completion,
@@ -700,7 +706,7 @@ class TaskRunProcess {
700706
}
701707

702708
async #handleExit(code: number) {
703-
logger.debug(`[${this.runId}] task run process exiting`, { code });
709+
logger.debug(`[${this.execution.run.id}] task run process exiting`, { code });
704710

705711
// Go through all the attempts currently pending and reject them
706712
for (const [id, status] of this._attemptStatuses.entries()) {

packages/cli-v3/src/workers/dev/worker-facade.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import {
66
type HandleErrorFunction,
77
DurableClock,
88
clock,
9+
logLevels,
10+
LogLevel,
11+
getEnvVar,
912
} from "@trigger.dev/core/v3";
1013

1114
__WORKER_SETUP__;
@@ -53,10 +56,18 @@ const devRuntimeManager = new DevRuntimeManager();
5356

5457
runtime.setGlobalRuntimeManager(devRuntimeManager);
5558

59+
const triggerLogLevel = getEnvVar("TRIGGER_LOG_LEVEL");
60+
61+
const configLogLevel = triggerLogLevel
62+
? triggerLogLevel
63+
: importedConfig
64+
? importedConfig.logLevel
65+
: __PROJECT_CONFIG__.logLevel;
66+
5667
const otelTaskLogger = new OtelTaskLogger({
5768
logger: otelLogger,
5869
tracer: tracer,
59-
level: "info",
70+
level: logLevels.includes(configLogLevel as any) ? (configLogLevel as LogLevel) : "log",
6071
});
6172

6273
logger.setGlobalTaskLogger(otelTaskLogger);

packages/cli-v3/src/workers/prod/backgroundWorker.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
CreateBackgroundWorkerResponse,
55
InferSocketMessageSchema,
66
ProdChildToWorkerMessages,
7+
ProdTaskRunExecution,
78
ProdTaskRunExecutionPayload,
89
ProdWorkerToChildMessages,
910
SemanticInternalAttributes,
@@ -200,6 +201,7 @@ export class ProdBackgroundWorker {
200201

201202
if (!this._taskRunProcess) {
202203
const taskRunProcess = new TaskRunProcess(
204+
payload.execution,
203205
this.path,
204206
{
205207
...this.params.env,
@@ -374,6 +376,7 @@ class TaskRunProcess {
374376
public onCancelCheckpoint = Evt.create<{ version?: "v1" }>();
375377

376378
constructor(
379+
private execution: ProdTaskRunExecution,
377380
private path: string,
378381
private env: NodeJS.ProcessEnv,
379382
private metadata: BackgroundWorkerProperties,
@@ -384,6 +387,7 @@ class TaskRunProcess {
384387
this._child = fork(this.path, {
385388
stdio: [/*stdin*/ "ignore", /*stdout*/ "pipe", /*stderr*/ "pipe", "ipc"],
386389
env: {
390+
...(this.execution.run.isTest ? { TRIGGER_LOG_LEVEL: "debug" } : {}),
387391
...this.env,
388392
OTEL_RESOURCE_ATTRIBUTES: JSON.stringify({
389393
[SemanticInternalAttributes.PROJECT_DIR]: this.worker.projectConfig.projectDir,

packages/cli-v3/src/workers/prod/worker-facade.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import {
99
HandleErrorFunction,
1010
DurableClock,
1111
clock,
12+
getEnvVar,
13+
logLevels,
14+
LogLevel,
1215
} from "@trigger.dev/core/v3";
1316
import "source-map-support/register.js";
1417

@@ -47,10 +50,18 @@ clock.setGlobalClock(durableClock);
4750
const tracer = new TriggerTracer({ tracer: otelTracer, logger: otelLogger });
4851
const consoleInterceptor = new ConsoleInterceptor(otelLogger);
4952

53+
const triggerLogLevel = getEnvVar("TRIGGER_LOG_LEVEL");
54+
55+
const configLogLevel = triggerLogLevel
56+
? triggerLogLevel
57+
: importedConfig
58+
? importedConfig.logLevel
59+
: __PROJECT_CONFIG__.logLevel;
60+
5061
const otelTaskLogger = new OtelTaskLogger({
5162
logger: otelLogger,
5263
tracer: tracer,
53-
level: "info",
64+
level: logLevels.includes(configLogLevel as any) ? (configLogLevel as LogLevel) : "log",
5465
});
5566

5667
logger.setGlobalTaskLogger(otelTaskLogger);

packages/core/src/v3/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ export { ProdRuntimeManager } from "./runtime/prodRuntimeManager";
3939
export { PreciseWallClock as DurableClock } from "./clock/preciseWallClock";
4040
export { TriggerTracer } from "./tracer";
4141

42-
export type { TaskLogger } from "./logger/taskLogger";
43-
export { OtelTaskLogger } from "./logger/taskLogger";
42+
export type { TaskLogger, LogLevel } from "./logger/taskLogger";
43+
export { OtelTaskLogger, logLevels } from "./logger/taskLogger";
4444
export { ConsoleInterceptor } from "./consoleInterceptor";
4545
export {
4646
flattenAttributes,

packages/core/src/v3/logger/taskLogger.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import { flattenAttributes } from "../utils/flattenAttributes";
77
import { ClockTime } from "../clock/clock";
88
import { clock } from "../clock-api";
99

10-
export type LogLevel = "log" | "error" | "warn" | "info" | "debug";
10+
export type LogLevel = "none" | "log" | "error" | "warn" | "info" | "debug";
1111

12-
const logLevels: Array<LogLevel> = ["error", "warn", "log", "info", "debug"];
12+
export const logLevels: Array<LogLevel> = ["none", "error", "warn", "log", "info", "debug"];
1313

1414
export type TaskLoggerConfig = {
1515
logger: Logger;
@@ -34,31 +34,31 @@ export class OtelTaskLogger implements TaskLogger {
3434
}
3535

3636
debug(message: string, properties?: Record<string, unknown>) {
37-
if (this._level < 4) return;
37+
if (this._level < 5) return;
3838

3939
this.#emitLog(message, this.#getTimestampInHrTime(), "debug", SeverityNumber.DEBUG, properties);
4040
}
4141

4242
log(message: string, properties?: Record<string, unknown>) {
43-
if (this._level < 2) return;
43+
if (this._level < 3) return;
4444

4545
this.#emitLog(message, this.#getTimestampInHrTime(), "log", SeverityNumber.INFO, properties);
4646
}
4747

4848
info(message: string, properties?: Record<string, unknown>) {
49-
if (this._level < 3) return;
49+
if (this._level < 4) return;
5050

5151
this.#emitLog(message, this.#getTimestampInHrTime(), "info", SeverityNumber.INFO, properties);
5252
}
5353

5454
warn(message: string, properties?: Record<string, unknown>) {
55-
if (this._level < 1) return;
55+
if (this._level < 2) return;
5656

5757
this.#emitLog(message, this.#getTimestampInHrTime(), "warn", SeverityNumber.WARN, properties);
5858
}
5959

6060
error(message: string, properties?: Record<string, unknown>) {
61-
if (this._level < 0) return;
61+
if (this._level < 1) return;
6262

6363
this.#emitLog(message, this.#getTimestampInHrTime(), "error", SeverityNumber.ERROR, properties);
6464
}

packages/core/src/v3/schemas/schemas.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export const Config = z.object({
4242
additionalPackages: z.string().array().optional(),
4343
additionalFiles: z.string().array().optional(),
4444
dependenciesToBundle: z.array(z.union([z.string(), RegexSchema])).optional(),
45+
logLevel: z.string().optional(),
4546
});
4647

4748
export type Config = z.infer<typeof Config>;

packages/core/src/v3/types/config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { LogLevel } from "../logger/taskLogger";
12
import { RetryOptions } from "../schemas";
23
import type { InstrumentationOption } from "@opentelemetry/instrumentation";
34

@@ -31,4 +32,13 @@ export interface ProjectConfig {
3132
* The OpenTelemetry instrumentations to enable
3233
*/
3334
instrumentations?: InstrumentationOption[];
35+
36+
/**
37+
* 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.
38+
*
39+
* We automatically set the logLevel to "debug" during test runs
40+
*
41+
* @default "log"
42+
*/
43+
logLevel?: LogLevel;
3444
}

packages/trigger-sdk/src/v3/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ export { retry, type RetryOptions } from "./retry";
77
import type { Context } from "./shared";
88
export type { Context };
99

10-
export { logger } from "@trigger.dev/core/v3";
10+
export { logger, type LogLevel } from "@trigger.dev/core/v3";

references/v3-catalog/src/trigger/logging.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import slugify from "@sindresorhus/slugify";
44
export const loggingTask = task({
55
id: "logging-task",
66
run: async () => {
7-
console.log(`Hello world 9 ${slugify("foo bar")}`);
8-
9-
return null;
7+
logger.error("This is an error message");
8+
logger.warn("This is a warning message");
9+
logger.log("This is a log message");
10+
logger.info("This is an info message");
11+
logger.debug("This is a debug message");
1012
},
1113
});
1214

references/v3-catalog/trigger.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ export const config: TriggerConfig = {
1919
additionalFiles: ["./wrangler/wrangler.toml"],
2020
dependenciesToBundle: [/@sindresorhus/, "escape-string-regexp"],
2121
instrumentations: [new OpenAIInstrumentation()],
22+
logLevel: "log",
2223
};

0 commit comments

Comments
 (0)