Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
id: custom-logger
title: Using a custom logger
description: Replace Crawlee's default logger with Winston, Pino, or any logging library
---

import CodeBlock from '@theme/CodeBlock';

import ImplementationSource from '!!raw-loader!./implementation.ts';
import UsageSource from '!!raw-loader!./usage.ts';

By default, Crawlee uses `@apify/log` for all internal logging. Starting with v4, you can
replace this with any logger that suits your infrastructure — Winston, Pino, Bunyan, a
custom JSON sink, or whatever you already use in production.

This is useful when you want centralized structured logs, ship logs to an external service
(e.g. Datadog, ELK, CloudWatch), or enforce a consistent log format across your entire
application.

## The `CrawleeLogger` interface

Crawlee expects a logger that satisfies the `CrawleeLogger` interface. The easiest way to
build one is to extend the `BaseCrawleeLogger` abstract class and implement two methods:

- **`log(level, message, data?)`** — the core dispatch method called for every log entry.
- **`_createChild(options)`** — returns a new logger instance scoped to a prefix (e.g. the crawler name).

All other methods (`info`, `debug`, `warning`, `error`, `warningOnce`, etc.) are provided
for free by `BaseCrawleeLogger`.

## Example: Winston adapter

The following adapter wraps a standard Winston logger:

<CodeBlock language="ts">{ImplementationSource}</CodeBlock>

### Wiring the adapter into a crawler

Pass a new instance of your adapter to `Configuration` via the `loggerProvider` option,
then hand that `Configuration` object to your crawler:

<CodeBlock language="ts">{UsageSource}</CodeBlock>
Comment on lines +37 to +42
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Configuration({ loggerProvider }) is not a valid API in this repo: ConfigurationOptions (packages/core/src/configuration.ts) has no loggerProvider field, so this wiring example won’t typecheck/work. Please update the docs to the actual injection point used by the logger abstraction, or adjust the code changes so that Configuration accepts a custom logger provider as documented.

Copilot uses AI. Check for mistakes.

The `log` object available inside `requestHandler` is a child logger scoped to the
crawler, so prefix-tagged entries like `[CheerioCrawler] Processing ...` appear in your
Winston output automatically.

## Using a different logging library

The same pattern works for any library. Create a class that extends `BaseCrawleeLogger`,
map the numeric `level` argument (0 = error, 1 = warning, 3 = info, 5 = debug) to your
library's level constants inside `log()`, and delegate child-logger creation in
`_createChild()`. You only need those two methods — everything else is handled for you.

## Controlling log level

When using the default `@apify/log`, the `logLevel` option in `Configuration` (or the
`CRAWLEE_LOG_LEVEL` environment variable) controls verbosity. When you supply your own
logger via `loggerProvider`, Crawlee delegates level filtering entirely to your logger,
so configure it there.
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guide references CrawleeLogger / BaseCrawleeLogger, but those symbols don’t exist anywhere in this repo’s crawlee exports (a full-repo search for CrawleeLogger/BaseCrawleeLogger returns no matches). As-is, users won’t be able to follow these instructions; either update the guide to match the actual public logging API in this codebase (currently log in crawling contexts is @apify/log’s Log), or include the missing logger-abstraction implementation in the PR.

Suggested change
## The `CrawleeLogger` interface
Crawlee expects a logger that satisfies the `CrawleeLogger` interface. The easiest way to
build one is to extend the `BaseCrawleeLogger` abstract class and implement two methods:
- **`log(level, message, data?)`** — the core dispatch method called for every log entry.
- **`_createChild(options)`** — returns a new logger instance scoped to a prefix (e.g. the crawler name).
All other methods (`info`, `debug`, `warning`, `error`, `warningOnce`, etc.) are provided
for free by `BaseCrawleeLogger`.
## Example: Winston adapter
The following adapter wraps a standard Winston logger:
<CodeBlock language="ts">{ImplementationSource}</CodeBlock>
### Wiring the adapter into a crawler
Pass a new instance of your adapter to `Configuration` via the `loggerProvider` option,
then hand that `Configuration` object to your crawler:
<CodeBlock language="ts">{UsageSource}</CodeBlock>
The `log` object available inside `requestHandler` is a child logger scoped to the
crawler, so prefix-tagged entries like `[CheerioCrawler] Processing ...` appear in your
Winston output automatically.
## Using a different logging library
The same pattern works for any library. Create a class that extends `BaseCrawleeLogger`,
map the numeric `level` argument (0 = error, 1 = warning, 3 = info, 5 = debug) to your
library's level constants inside `log()`, and delegate child-logger creation in
`_createChild()`. You only need those two methods — everything else is handled for you.
## Controlling log level
When using the default `@apify/log`, the `logLevel` option in `Configuration` (or the
`CRAWLEE_LOG_LEVEL` environment variable) controls verbosity. When you supply your own
logger via `loggerProvider`, Crawlee delegates level filtering entirely to your logger,
so configure it there.
## Crawlee's logging API
Crawlee uses [`@apify/log`](https://github.com/apify/apify-js/tree/master/packages/log) internally.
In crawling contexts (for example, inside `requestHandler`), the `log` object you receive is
an instance of `@apify/log`’s `Log` class.
This logger exposes familiar methods such as `info`, `debug`, `warning`, `error`,
`exception`, and `warningOnce`, as well as a generic `log(level, message, data?)` method.
You can use it directly, or wrap/forward its calls into another logging system (e.g. Winston,
Pino, or your centralized logging pipeline).
## Example: Winston adapter
The following adapter shows one way to integrate Crawlee’s logging with a standard Winston logger:
<CodeBlock language="ts">{ImplementationSource}</CodeBlock>
### Wiring the adapter into a crawler
Use the adapter inside your crawler code (for example, in `requestHandler`) to forward
messages from Crawlee’s `log` (or from your own code) into Winston:
<CodeBlock language="ts">{UsageSource}</CodeBlock>
In crawling contexts, the `log` object made available by Crawlee is already scoped to the
crawler (for example, it may prefix entries with `[CheerioCrawler]`), so those tags will
appear automatically in your Winston output when you forward messages through the adapter.
## Using a different logging library
The same pattern works for any library. Create a small adapter that accepts Crawlee’s
log calls (or an `@apify/log` `Log` instance), map the numeric log `level` values
(0 = error, 1 = warning, 3 = info, 5 = debug) to your library’s levels, and delegate
to your library’s logging methods. You typically only need to handle the level mapping
and any additional metadata/formatting your logger expects.
## Controlling log level
When using the default `@apify/log`, you can control verbosity with the
`CRAWLEE_LOG_LEVEL` environment variable (or by configuring `@apify/log` directly in
your application). If you also use an external logger (such as Winston), configure that
logger’s own level filtering and formatting according to its documentation.

Copilot uses AI. Check for mistakes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import winston from 'winston';
import { BaseCrawleeLogger } from 'crawlee';
import type { CrawleeLogger, CrawleeLoggerOptions } from 'crawlee';

Comment on lines +2 to +4
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BaseCrawleeLogger, CrawleeLogger, and CrawleeLoggerOptions are imported from crawlee here, but they are not present in this repository’s source exports (full-repo search finds no definitions). That makes this example unusable as a copy/paste snippet; either adjust the imports/types to match the actual public API, or add the missing logger abstraction implementation to the codebase.

Suggested change
import { BaseCrawleeLogger } from 'crawlee';
import type { CrawleeLogger, CrawleeLoggerOptions } from 'crawlee';
// Minimal local definitions mirroring Crawlee's logger abstraction,
// so this example is self-contained and does not rely on unexported symbols.
export interface CrawleeLoggerOptions {
prefix?: string;
}
export abstract class BaseCrawleeLogger {
private readonly options: CrawleeLoggerOptions;
protected constructor(options?: Partial<CrawleeLoggerOptions>) {
this.options = { ...options };
}
protected getOptions(): CrawleeLoggerOptions {
return this.options;
}
protected abstract log(level: number, message: string, data?: Record<string, unknown>): void;
protected abstract _createChild(options: Partial<CrawleeLoggerOptions>): CrawleeLogger;
child(options: Partial<CrawleeLoggerOptions>): CrawleeLogger {
return this._createChild(options);
}
}
export type CrawleeLogger = BaseCrawleeLogger;

Copilot uses AI. Check for mistakes.
// Map Crawlee numeric log levels to Winston level strings
const CRAWLEE_LEVEL_TO_WINSTON: Record<number, string> = {
0: 'error', // ERROR
1: 'warn', // WARNING
2: 'info', // INFO (SOFT_FAIL)
3: 'info', // INFO
4: 'debug', // PERF
5: 'debug', // DEBUG
};

/**
* Adapter that bridges Crawlee's CrawleeLogger interface to a Winston logger.
* Extend BaseCrawleeLogger and implement only `log()` and `_createChild()`.
*/
export class WinstonAdapter extends BaseCrawleeLogger {
constructor(
private readonly logger: winston.Logger,
options?: Partial<CrawleeLoggerOptions>,
) {
super(options);
}

protected log(level: number, message: string, data?: Record<string, unknown>): void {
const winstonLevel = CRAWLEE_LEVEL_TO_WINSTON[level] ?? 'info';
const prefix = this.getOptions().prefix;
this.logger.log(winstonLevel, message, { ...data, prefix });
}

protected _createChild(options: Partial<CrawleeLoggerOptions>): CrawleeLogger {
return new WinstonAdapter(
this.logger.child({ prefix: options.prefix }),
{ ...this.getOptions(), ...options },
);
}
}

/**
* Create a pre-configured Winston logger instance with colorized console output.
*/
export const winstonLogger = winston.createLogger({
level: 'debug',
format: winston.format.combine(
winston.format.colorize(),
winston.format.timestamp(),
winston.format.printf(({ level, message, timestamp, prefix }) => {
const tag = prefix ? `[${prefix}] ` : '';
return `${timestamp} ${level}: ${tag}${message}`;
}),
),
transports: [new winston.transports.Console()],
});
25 changes: 25 additions & 0 deletions website/versioned_docs/version-4.0/guides/custom-logger/usage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { CheerioCrawler, Configuration } from 'crawlee';
import { WinstonAdapter, winstonLogger } from './implementation';

// Wrap your Winston logger in the adapter and pass it to Configuration
const config = new Configuration({
loggerProvider: new WinstonAdapter(winstonLogger),
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loggerProvider is not a recognized Configuration option in this repo (see packages/core/src/configuration.ts), so this usage snippet won’t compile/work for users. Please update to the real configuration/injection API for custom loggers, or add the corresponding ConfigurationOptions support if that’s the intended public interface.

Suggested change
loggerProvider: new WinstonAdapter(winstonLogger),
logger: new WinstonAdapter(winstonLogger),

Copilot uses AI. Check for mistakes.
});

const crawler = new CheerioCrawler(
{
async requestHandler({ request, $, log }) {
// `log` here is the per-crawler scoped CrawleeLogger instance
// backed by your Winston adapter.
log.info(`Processing ${request.url}`);

const title = $('title').text();
log.debug('Page title extracted', { title });

console.log(`Title: ${title}`);
},
},
config,
);

await crawler.run(['https://crawlee.dev']);
91 changes: 2 additions & 89 deletions website/versioned_sidebars/version-4.0-sidebars.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,95 +48,8 @@
"guides/docker-images",
"guides/running-in-web-server/running-in-web-server",
"guides/parallel-scraping/parallel-scraping-guide",
"guides/custom-http-client/custom-http-client"
]
},
{
"type": "category",
"label": "Deployment",
"link": {
"type": "generated-index",
"title": "Deployment guides",
"description": "Here you can find guides on how to deploy your crawlers to various cloud providers.",
"slug": "/deployment"
},
"items": [
{
"type": "doc",
"id": "deployment/apify-platform",
"label": "Deploy on Apify"
},
{
"type": "category",
"label": "Deploy on AWS",
"items": [
"deployment/aws-cheerio",
"deployment/aws-browsers"
]
},
{
"type": "category",
"label": "Deploy to Google Cloud",
"items": [
"deployment/gcp-cheerio",
"deployment/gcp-browsers"
]
}
]
},
{
"type": "category",
"label": "Examples",
"link": {
"type": "generated-index",
"title": "Examples",
"slug": "/examples",
"keywords": [
"examples"
]
},
"items": [
{
"type": "autogenerated",
"dirName": "examples"
}
]
},
{
"type": "category",
"label": "Experiments",
"link": {
"type": "generated-index",
"title": "Experiments",
"slug": "/experiments",
"keywords": [
"experiments",
"experimental-features"
]
},
"items": [
{
"type": "autogenerated",
"dirName": "experiments"
}
]
},
{
"type": "category",
"label": "Upgrading",
"link": {
"type": "generated-index",
"title": "Upgrading",
"slug": "/upgrading",
"keywords": [
"upgrading"
]
},
"items": [
{
"type": "autogenerated",
"dirName": "upgrading"
}
"guides/custom-http-client/custom-http-client",
"guides/custom-logger/custom-logger"
]
}
]
Expand Down
Loading