Skip to content

feat(pino-transport): Add functionality to send logs to sentry #16667

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,6 @@ below:
Provides the integration for Session Replay.
- [`@sentry/core`](https://github.com/getsentry/sentry-javascript/tree/master/packages/core): The base for all
JavaScript SDKs with interfaces, type definitions and base classes.
- [`@sentry/pino-transport`](https://github.com/getsentry/sentry-javascript/tree/master/packages/pino-transport): Pino
transport for automatically sending log messages to Sentry.

## Bug Bounty Program

Expand Down
265 changes: 251 additions & 14 deletions packages/pino-transport/README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,267 @@
# @sentry/pino-transport
<p align="center">
<a href="https://sentry.io/?utm_source=github&utm_medium=logo" target="_blank">
<img src="https://sentry-brand.storage.googleapis.com/sentry-wordmark-dark-280x84.png" alt="Sentry" width="280" height="84">
</a>
</p>

[![npm version](https://img.shields.io/npm/v/@sentry/pino-transport.svg)](https://www.npmjs.com/package/@sentry/pino-transport)
[![npm dm](https://img.shields.io/npm/dm/@sentry/pino-transport.svg)](https://www.npmjs.com/package/@sentry/pino-transport)
[![npm dt](https://img.shields.io/npm/dt/@sentry/pino-transport.svg)](https://www.npmjs.com/package/@sentry/pino-transport)
# Official Sentry Pino Transport

**This package is currently in alpha. Breaking changes may still occur.**
[![npm version](https://img.shields.io/npm/v/@sentry/solid.svg)](https://www.npmjs.com/package/@sentry/solid)
[![npm dm](https://img.shields.io/npm/dm/@sentry/solid.svg)](https://www.npmjs.com/package/@sentry/solid)
[![npm dt](https://img.shields.io/npm/dt/@sentry/solid.svg)](https://www.npmjs.com/package/@sentry/solid)

A Pino transport for integrating [Pino](https://github.com/pinojs/pino) logging with [Sentry](https://sentry.io). This transport automatically captures log messages as Sentry events and breadcrumbs, making it easy to monitor your application's logs in Sentry.
**WARNING**: This transport is in a **pre-release alpha**. The API is unstable and may change at any time.

A Pino transport for sending logs to Sentry using the Sentry JavaScript SDK.

This transport forwards Pino logs to Sentry, allowing you to view and analyze your application logs alongside your errors and performance data in Sentry.

## Installation

```bash
npm install @sentry/node @sentry/pino-transport
npm install @sentry/pino-transport pino
# or
yarn add @sentry/node @sentry/pino-transport
yarn add @sentry/pino-transport pino
# or
pnpm add @sentry/pino-transport pino
```

## Usage
## Requirements

- Node.js 18+
- Pino v8 or v9
- `@sentry/node` SDK with `_experiments.enableLogs: true`

TODO: Add usage instructions
## Setup

## Requirements
First, make sure Sentry is initialized with logging enabled:

```javascript
import * as Sentry from '@sentry/node';

Sentry.init({
dsn: 'YOUR_DSN',
_experiments: {
enableLogs: true,
},
});
```

Then create a Pino logger with the Sentry transport:

```javascript
import pino from 'pino';

const logger = pino({
transport: {
target: '@sentry/pino-transport',
options: {
// Optional: filter which log levels to send to Sentry
levels: ['error', 'fatal'], // defaults to all levels
},
},
});

// Now your logs will be sent to Sentry
logger.info('This is an info message');
logger.error('This is an error message');
```

## Configuration Options

The transport accepts the following options:

### `logLevels`

**Type:** `Array<'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'>`

**Default:** `['trace', 'debug', 'info', 'warn', 'error', 'fatal']` (all log levels)

Use this option to filter which log severity levels should be sent to Sentry.

```javascript
const transport = pino.transport({
target: '@sentry/pino-transport',
options: {
logLevels: ['warn', 'error', 'fatal'], // Only send warnings and above
},
});
```

## Log Level Mapping

Pino log levels are automatically mapped to Sentry log severity levels:

| Pino Level | Pino Numeric | Sentry Level |
| ---------- | ------------ | ------------ |
| trace | 10 | trace |
| debug | 20 | debug |
| info | 30 | info |
| warn | 40 | warn |
| error | 50 | error |
| fatal | 60 | fatal |

### Custom Levels Support

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm still undecided about this behaviour.

See https://getpino.io/#/docs/api?id=opt-customlevels for details about pino custom levels.

What does everyone think?

Copy link
Member

Choose a reason for hiding this comment

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

Do you mean the level ranges?

Currently, you're "cutting" it in the middle of the range (or are those already the Pino level cuts?). How about cutting it at the point where the level changes? Like this:

  • 0-19trace
  • 20-29debug
  • 30-39info
  • 40-49warn
  • 50-59error
  • 60+fatal

But I am not quite sure how pino works here 🤔

Copy link
Member

Choose a reason for hiding this comment

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

Bit hard to map, your call!

Copy link
Member Author

Choose a reason for hiding this comment

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

How about cutting it at the point where the level changes

I think that feels better, just tried it out in my test app. Feels easier to explain to users as well.

Custom numeric levels are mapped to Sentry levels using ranges, so levels like `11`, `23`, or `42` will map correctly:

- `0-19` → `trace`
- `20-29` → `debug`
- `30-39` → `info`
- `40-49` → `warn`
- `50-59` → `error`
- `60+` → `fatal`

```javascript
import pino from 'pino';

const logger = pino({
customLevels: {
critical: 55, // Maps to 'fatal' (55+ range)
notice: 35, // Maps to 'warn' (35-44 range)
verbose: 11, // Maps to 'trace' (0-14 range)
},
transport: {
target: '@sentry/pino-transport',
},
});

logger.critical('Critical issue occurred'); // → Sent as 'fatal' to Sentry
logger.notice('Important notice'); // → Sent as 'warn' to Sentry
logger.verbose('Detailed information'); // → Sent as 'trace' to Sentry
```

#### Custom Level Attributes

When using custom string levels, the original level name is preserved as `sentry.pino.level` attribute for better traceability:

```javascript
// Log entry in Sentry will include:
// {
// level: 'warn', // Mapped Sentry level
// message: 'Audit event',
// attributes: {
// 'sentry.pino.level': 'audit', // Original custom level name
// 'sentry.origin': 'auto.logging.pino',
// // ... other log attributes
// }
// }
```

### Custom Message Key

The transport respects Pino's `messageKey` configuration:

```javascript
const logger = pino({
messageKey: 'message', // Use 'message' instead of default 'msg'
transport: {
target: '@sentry/pino-transport',
},
});

logger.info({ message: 'Hello world' }); // Works correctly with custom messageKey
```

### Nested Key Support

The transport automatically supports Pino's `nestedKey` configuration, which is used to avoid property conflicts by nesting logged objects under a specific key. When `nestedKey` is configured, the transport flattens these nested properties using dot notation for better searchability in Sentry.

```javascript
const logger = pino({
nestedKey: 'payload', // Nest logged objects under 'payload' key
transport: {
target: '@sentry/pino-transport',
},
});

const conflictingObject = {
level: 'hi', // Conflicts with Pino's level
time: 'never', // Conflicts with Pino's time
foo: 'bar',
userId: 123,
};

logger.info(conflictingObject);

// Without nestedKey, this would cause property conflicts
// With nestedKey, Pino creates: { level: 30, time: 1234567890, payload: conflictingObject }
// The transport flattens it to:
// {
// level: 'info',
// message: undefined,
// attributes: {
// 'payload.level': 'hi', // Flattened nested properties
// 'payload.time': 'never',
// 'payload.foo': 'bar',
// 'payload.userId': 123,
// 'sentry.origin': 'auto.logging.pino',
// }
// }
```

This flattening ensures that no property conflicts occur between logged objects and Pino's internal properties.

## Usage Examples

### Basic Logging

```javascript
import pino from 'pino';

const logger = pino({
transport: {
target: '@sentry/pino-transport',
},
});

logger.trace('Starting application');
logger.debug('Debug information', { userId: 123 });
logger.info('User logged in', { userId: 123, username: 'john_doe' });
logger.warn('Deprecated API used', { endpoint: '/old-api' });
logger.error('Database connection failed', { error: 'Connection timeout' });
logger.fatal('Application crashed', { reason: 'Out of memory' });
```

### Multiple Transports

```javascript
import pino from 'pino';

const logger = pino({
transport: {
targets: [
{
target: 'pino-pretty',
options: { colorize: true },
level: 'debug',
},
{
target: '@sentry/pino-transport',
options: {
logLevels: ['warn', 'error', 'fatal'],
},
level: 'warn',
},
],
},
});
```

## Troubleshooting

### Logs not appearing in Sentry

1. Ensure `_experiments.enableLogs: true` is set in your Sentry configuration.
2. Check that your DSN is correct and the SDK is properly initialized.
3. Verify the log level is included in the `levels` configuration.
4. Check your Sentry organization stats page to see if logs are being received by Sentry.

## Related Documentation

- Node.js 18 or higher
- Pino 8.0.0 or higher
- @sentry/node must be configured in your application
- [Sentry Logs Documentation](https://docs.sentry.io/platforms/javascript/guides/node/logs/)
- [Pino Documentation](https://getpino.io/)
- [Pino Transports](https://getpino.io/#/docs/transports)

## License

Expand Down
4 changes: 3 additions & 1 deletion packages/pino-transport/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
"access": "public"
},
"dependencies": {
"@sentry/core": "9.31.0"
"@sentry/node": "9.31.0",
"@sentry/core": "9.31.0",
"pino-abstract-transport": "^2.0.0"
},
"peerDependencies": {
"pino": "^8.0.0 || ^9.0.0"
Expand Down
8 changes: 8 additions & 0 deletions packages/pino-transport/src/debug-build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
declare const __DEBUG_BUILD__: boolean;

/**
* This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code.
*
* ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking.
*/
export const DEBUG_BUILD = __DEBUG_BUILD__;
Loading
Loading