Skip to main content

Logging

Webda uses structured logging via the useLog factory from @webda/workout. Every service and model should use useLog rather than console.* for consistent, filterable log output.

useLog(name)​

import { useLog } from "@webda/workout";

const log = useLog("MyService");

log.info("Service started", { url: "https://api.example.com" });
log.debug("Processing item", { id: "abc123" });
log.warn("Retry attempt", { attempt: 3 });
log.error("Failed to connect", { host: "db.example.com", error: err.message });

Log levels​

MethodLevelUse for
log.trace(msg, data?)TRACEVery verbose, high-frequency internals
log.debug(msg, data?)DEBUGDevelopment diagnostics
log.info(msg, data?)INFONormal operational events
log.warn(msg, data?)WARNUnexpected but non-fatal conditions
log.error(msg, data?)ERRORErrors that need attention

Using in services​

All services should declare a module-level logger:

import { Service, ServiceParameters, Bean } from "@webda/core";
import { useLog } from "@webda/workout";

@Bean
export class PublisherService extends Service {
protected log = useLog("PublisherService");

async init(): Promise<this> {
await super.init();
this.log.info("Publisher initialized");
return this;
}

async publish(slug: string, platform: string): Promise<void> {
this.log.debug("Starting publish", { slug, platform });
try {
await this.doPublish(slug, platform);
this.log.info("Published successfully", { slug, platform });
} catch (err: any) {
this.log.error("Publish failed", { slug, platform, error: err.message });
throw err;
}
}
}

Structured data logging​

Pass a second argument (object) to any log method for structured context. This context is forwarded to the log sink as metadata:

log.info("Request completed", {
method: "POST",
path: "/posts",
statusCode: 201,
durationMs: 45
});

Output (JSON sink):

{
"level": "info",
"name": "PublisherService",
"message": "Request completed",
"method": "POST",
"path": "/posts",
"statusCode": 201,
"durationMs": 45,
"timestamp": "2026-04-25T12:00:00.000Z"
}

Log sinks — @webda/workout​

useLog is backed by @webda/workout. The default sink writes to stdout in a human-readable format during development, and can be configured for JSON output in production:

{
"parameters": {
"log": {
"level": "INFO",
"format": "json"
}
}
}

Available formats: "text" (default, colorized), "json", "logstash".

Memory log sink (testing)​

The @testWrapper decorator in @webda/core/test uses the memory log sink to capture all log output during a test and write it to a reports/ file on failure:

import { WebdaTest } from "@webda/core/test";
import { testWrapper } from "@webda/workout";

class MyServiceTest extends WebdaTest {
@testWrapper
async testInit() {
const service = await this.getService("MyService");
// If this test fails, all logs are written to reports/MyServiceTest_testInit.log
}
}

Log correlation​

In production, logs can be correlated across services using request IDs. The framework automatically adds a requestId field to all logs within a request context when the Router generates one.

Do not use console.log​

The ESLint config for Webda packages enforces using useLog instead of console.*. Using console.log bypasses the structured log pipeline and the memory sink (breaking test failure log capture).

Verify​

# Run @webda/workout tests
cd packages/workout
pnpm test
✓ packages/workout — all tests pass

See also​