> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt # Winston Logs installation Canonical URL: https://posthog.com/docs/logs/installation/winston > **Cloud region:** This page uses US Cloud hosts. For an EU Cloud project, use `https://eu.i.posthog.com` instead of `https://us.i.posthog.com` (ingestion host), and use `https://eu.posthog.com` instead of `https://app.posthog.com` (app host for links). ## On this page - [Install OpenTelemetry packages](#install-opentelemetry-packages) - [Get your project token](#get-your-project-token) - [Create an instrumentation file](#create-an-instrumentation-file) - [Load the instrumentation before your app](#load-the-instrumentation-before-your-app) - [Use your Winston logger](#use-your-winston-logger) - [Test your setup](#test-your-setup) - [Next steps](#next-steps) - [Troubleshooting](#troubleshooting) Send your existing [Winston](https://github.com/winstonjs/winston) logs to PostHog with the OpenTelemetry Winston instrumentation. You don't need to change your logger calls. Requires Winston v3. 1. 1 ## Install OpenTelemetry packages Required ```bash npm install @opentelemetry/sdk-node @opentelemetry/exporter-logs-otlp-http @opentelemetry/sdk-logs @opentelemetry/resources @opentelemetry/instrumentation-winston @opentelemetry/winston-transport ``` The instrumentation only sends logs when `@opentelemetry/winston-transport` is installed. 2. 2 ## Get your project token Required You'll need your PostHog project token to authenticate log requests. This is the same key you use for capturing events and exceptions with the PostHog SDK. > **Important:** Use your **project token** which starts with `phc_`. Do **not** use a personal API key (which starts with `phx_`). You can find your project token in [Project Settings](https://app.posthog.com/settings). 3. 3 ## Create an instrumentation file Required Create an `instrumentation.js` file. It starts the OpenTelemetry SDK and tells it to send every Winston log to PostHog. instrumentation.js ```javascript const { NodeSDK } = require('@opentelemetry/sdk-node'); const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http'); const { BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs'); const { resourceFromAttributes } = require('@opentelemetry/resources'); const { WinstonInstrumentation } = require('@opentelemetry/instrumentation-winston'); const sdk = new NodeSDK({ resource: resourceFromAttributes({ 'service.name': 'my-node-service', }), logRecordProcessors: [ new BatchLogRecordProcessor({ exporter: new OTLPLogExporter({ url: 'https://us.i.posthog.com/i/v1/logs', headers: { 'Authorization': 'Bearer ' } }) }) ], instrumentations: [new WinstonInstrumentation()] }); sdk.start(); // Send buffered logs before the process exits process.on('SIGTERM', () => { sdk.shutdown().finally(() => process.exit(0)); }); ``` If your app already handles `SIGTERM` (for example, to drain open requests), don't add a second handler. Export `sdk` and call `await sdk.shutdown()` at the end of your existing shutdown code instead. 4. 4 ## Load the instrumentation before your app Required The instrumentation must load before your app imports `winston`. Otherwise it can't patch Winston and no logs are sent. Use the `--require` flag when you start your app: ```bash node --require ./instrumentation.js app.js ``` If you can't change the start command (for example, on a hosting platform), set `NODE_OPTIONS` instead: ```bash NODE_OPTIONS="--require ./instrumentation.js" npm start ``` **If your app uses ES modules** `--require` doesn't patch `import` statements. Install the OpenTelemetry instrumentation package: ```bash npm install @opentelemetry/instrumentation ``` Name the file `instrumentation.mjs`, change the `require` calls to `import` statements, and register the OpenTelemetry loader hook before you start the SDK: instrumentation.mjs ```javascript import { register } from 'node:module'; // ...the same imports as instrumentation.js register('@opentelemetry/instrumentation/hook.mjs', import.meta.url); // ...the same SDK setup as instrumentation.js ``` Then start your app with `--import`: ```bash node --import ./instrumentation.mjs app.js ``` 5. 5 ## Use your Winston logger Required Keep using Winston as before. Your existing transports still work, and a copy of each log goes to PostHog: ```javascript const winston = require('winston'); const logger = winston.createLogger({ level: 'info', defaultMeta: { service: 'checkout' }, transports: [new winston.transports.Console()], }); logger.info('Server started'); logger.warn('Payment retry', { userId: 'user_123', retries: 3 }); logger.error('Query failed', new Error('Database connection failed')); ``` PostHog receives each Winston record like this: | Winston | PostHog Logs | | --- | --- | | Message | Log body | | Level (`info`, `warn`, `error`, etc.) | Severity | | Metadata, `defaultMeta`, and child logger fields | Log attributes | | `Error` objects | Error message appended to the body, a `stack` attribute, and `exception.type`, `exception.message`, and `exception.stacktrace` attributes | Logs below the logger's `level` are not sent. In the example above, `logger.debug()` calls don't reach PostHog. To [link logs to a person and their session replay](https://posthog.com/docs/logs/link-session-replay.md), add the `posthogDistinctId` and `sessionId` fields. A child logger adds them to every log in a request: ```javascript const requestLogger = logger.child({ posthogDistinctId: userId, sessionId: sessionId, }); requestLogger.info('Checkout completed'); ``` 6. 6 ## Test your setup Recommended Once everything is configured, test that logs are flowing into PostHog: 1. Start your app with the instrumentation loaded and trigger a log 2. Check the PostHog Logs interface for your log entries 3. Verify the logs appear in your project [View your logs in PostHog](https://app.posthog.com/logs) 8. ## Next steps Checkpoint *What you can do with your logs* | Action | Description | | --- | --- | | **[Why you need logs](https://posthog.com/docs/logs/basics.md)** | What logs show you that nothing else does | | **[Search logs](https://posthog.com/docs/logs/search.md)** | Use the search interface to find specific log entries | | **Filter by level** | Filter by `INFO`, `WARN`, `ERROR`, etc. | | **[Link session replay](https://posthog.com/docs/logs/link-session-replay.md)** | Connect logs to users and session replays by passing `posthogDistinctId` and `sessionId` | | **[Link logs to a person](https://posthog.com/docs/logs/link-person.md)** | Surface every log emitted on behalf of a user on their PostHog person profile | | **[Logging best practices](https://posthog.com/docs/logs/best-practices.md)** | Learn what to log, how to structure logs, and patterns that make logs useful in production | [Troubleshoot common issues](https://posthog.com/docs/logs/troubleshooting.md) ## Troubleshooting **Logs not appearing in PostHog** 1. Check that `@opentelemetry/winston-transport` is installed. Without it, the instrumentation doesn't send logs. 2. Make sure the instrumentation file loads before `winston`. If your app uses ES modules, you need the `--import` flag and the loader hook. 3. Check your Winston version. Log sending requires Winston v3. 4. The SDK sends logs in batches, so a short-lived script that ends on its own loses the last batch. Add `module.exports = sdk;` to `instrumentation.js` and call `await require('./instrumentation').shutdown()` before the script ends. For general Logs troubleshooting (authentication, connection issues, format problems), see the [Logs troubleshooting guide](https://posthog.com/docs/logs/troubleshooting.md).