apify/crawlee · error · ServiceConflictError

ServiceConflictError('Logger', logger, this.#logger)

Error message

ServiceConflictError('Logger', logger, this.#logger)

What it means

setLogger() throws ServiceConflictError if a different CrawleeLogger is registered after one already exists in the ServiceLocator. The library assumes a single logger instance so all components emit logs through the same sink.

Source

Thrown at packages/core/src/service_locator.ts:287

        }

        this.#storageBackend = storageBackend;
    }

    getLogger(): CrawleeLogger {
        if (!this.#logger) {
            this.#logger = new ApifyLogAdapter(log);
        }
        return this.#logger;
    }

    setLogger(logger: CrawleeLogger): void {
        if (this.#logger === logger) {
            return;
        }

        if (this.#logger) {
            throw new ServiceConflictError('Logger', logger, this.#logger);
        }

        this.#logger = logger;
    }

    getChildLog(prefix: string): CrawleeLogger {
        return this.getLogger().child({ prefix });
    }

    getStorageInstanceManager(): StorageInstanceManager {
        if (!ServiceLocator.#storageInstanceManager) {
            ServiceLocator.#storageInstanceManager = new StorageInstanceManager();
        }
        return ServiceLocator.#storageInstanceManager;
    }

    reset(): void {
        this.#configuration = undefined;

View on GitHub (pinned to dbe57fb09c)

Solutions

  1. Inject the custom logger before any crawlee component initializes (before crawler construction or storage access).
  2. Reuse getLogger() and wrap/extend it rather than replacing it.
  3. Call setLogger() only once per ServiceLocator lifetime.
  4. Prefer configuring log level via Log.getLogLevel()/log options rather than swapping logger instances.

Example fix

// before
const crawler = new PlaywrightCrawler(...); // default logger registered
locator.setLogger(myLogger); // throws

// after
const locator = new ServiceLocator();
locator.setLogger(myLogger);
const crawler = new PlaywrightCrawler({ ..., serviceLocator: locator });
Defensive patterns

Strategy: validation

Validate before calling

let existing;
try { existing = locator.getLogger(); } catch { existing = null; }
if (existing && existing !== myLogger) { /* logger already registered */ }

Try / catch

try {
    locator.setLogger(logger);
} catch (e) {
    if (e instanceof ServiceConflictError) {
        const base = locator.getLogger();
        logger = Object.assign(Object.create(Object.getPrototypeOf(base)), logger);
    } else throw e;
}

Prevention

When it happens

Trigger: Calling setLogger(loggerA) then setLogger(loggerB); or using the default logger implicitly and later trying to inject a custom logger via the locator.

Common situations: Installing a custom log implementation after crawlee initialization, or in tests where multiple setups each register a logger on the shared locator.

Related errors


AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30). Data as JSON: /api/errors/4f2628e01d846c09. Report an issue: GitHub.