Logging
Sidequest.js uses Winston for structured logging throughout the framework, and can send its logs to pino instead. You can also use the logger in your jobs to add debugging information, monitor execution, and track important events.
Using the Logger in Jobs
Import and use the logger function to add logging to your jobs:
import { Job, logger } from "@sidequest/core";
export class MyJob extends Job {
async run(data: any) {
const log = logger("MyJob"); // Create a logger instance with a label
log.info("Processing job", { data });
try {
const result = await this.processData(data);
log.info("Job completed successfully", { result });
return result;
} catch (error) {
log.error("Job failed", { error: error.message, data });
throw error;
}
}
}Of course, the plain ol' console.log can be used here, but using the logger provides structured output and better integration with Sidequest's logging system.
Logger Labels
Use descriptive labels to identify different components and job types:
// Use job class name as label
const log = logger("EmailJob");
// Use more specific labels for complex jobs
const log = logger("PaymentProcessor.CreditCard");
// Include operation type
const log = logger("DataSync.ImportUsers");Log Levels
Different log levels serve different purposes:
Error Level
Use for serious problems that prevent job completion:
log.error("Database connection failed", {
error: error.message,
connectionString: this.sanitizeConnectionString(connectionString),
retryAttempt: this.attempt,
});Warning Level
Use for recoverable issues or unexpected conditions:
log.warn("API rate limit reached, will retry", {
apiEndpoint: endpoint,
retryAfter: retryAfterSeconds,
attempt: this.attempt,
});Info Level
Use for important business events and job progress:
log.info("Payment processed successfully", {
paymentId: payment.id,
amount: payment.amount,
currency: payment.currency,
userId: payment.userId,
});Debug Level
Use for detailed execution information (usually disabled in production):
log.debug("Validating payment data", {
paymentData: this.sanitizePaymentData(paymentData),
validationRules: validationRules,
});Using pino
If your app already logs with pino (for example through Fastify or AdonisJS), Sidequest can write its logs through pino too, so both use the same format.
pino is an optional peer dependency: Sidequest does not install it, so you must install it yourself before selecting the adapter:
npm install pinoyarn add pinopnpm add pinoWARNING
If pino is not installed, Sidequest.start() and Sidequest.configure() fail with an error asking you to install it.
Then select the pino adapter:
await Sidequest.start({
logger: {
level: "info",
adapter: "pino",
options: { base: { service: "jobs" } }, // passed to pino()
},
});Every Sidequest entry then goes through pino, in your app process, the engine process and the job worker threads, including the entries you write with logger() inside jobs. The label becomes a scope field:
{ "level": 30, "time": 1790885583530, "service": "jobs", "scope": "Core", "msg": "Running job #1 - HelloJob" }A few things to keep in mind:
logger.leveldecides which entries are logged. Alevelinsideoptionsis ignored, andlogger.jsonhas no effect.optionsare sent to the engine process and the worker threads, so they must be serializable. Options that take functions, such astimestamp,mixin,formatters,serializersorhooks, are not supported:Sidequest.start()fails with an error ifoptionscontains one.Errorobjects in the metadata (for examplelog.error("Job failed", { error })) are written the way pino writeserr, with theirtype,messageandstack.- Each process and worker thread creates its own pino instance, which writes to stdout synchronously so that a worker thread waiting for its next job does not hold entries back. Pipe stdout to your log collector or to pino-pretty to format it.
- Avoid pino's
transportoption with the defaultrunner: "thread": a transport needs the worker thread's event loop to deliver entries, but the thread is blocked while it waits for the next job, so entries logged inside jobs are not delivered. Entries from your app process and the engine process are not affected.
Best Practices
- Use structured logging: Include relevant context as metadata objects
- Choose appropriate log levels: Error for failures, info for business events, debug for detailed tracing
- Sanitize sensitive data: Never log passwords, API keys, or PII
- Include timing information: Log start/end times and durations for performance monitoring
- Add job context: Include job ID, attempt number, and other relevant identifiers
- Use consistent labels: Use descriptive, consistent logger labels across your application
- Log business events: Log important business outcomes, not just technical events
