From f102d1abcc79ee6235289a8dc6e96aedd43b73d7 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 2 Oct 2025 09:59:22 -0400 Subject: [PATCH 1/2] feat: Remove @sentry/pino-transport package --- .github/ISSUE_TEMPLATE/bug.yml | 1 - .../e2e-tests/verdaccio-config/config.yaml | 6 - package.json | 1 - packages/pino-transport/.eslintrc.js | 12 - packages/pino-transport/LICENSE | 16 - packages/pino-transport/README.md | 266 ------- packages/pino-transport/package.json | 79 -- packages/pino-transport/rollup.npm.config.mjs | 3 - packages/pino-transport/src/debug-build.ts | 8 - packages/pino-transport/src/index.ts | 244 ------ packages/pino-transport/test/index.test.ts | 708 ------------------ packages/pino-transport/tsconfig.json | 10 - packages/pino-transport/tsconfig.test.json | 7 - packages/pino-transport/tsconfig.types.json | 10 - packages/pino-transport/vite.config.ts | 10 - 15 files changed, 1381 deletions(-) delete mode 100644 packages/pino-transport/.eslintrc.js delete mode 100644 packages/pino-transport/LICENSE delete mode 100644 packages/pino-transport/README.md delete mode 100644 packages/pino-transport/package.json delete mode 100644 packages/pino-transport/rollup.npm.config.mjs delete mode 100644 packages/pino-transport/src/debug-build.ts delete mode 100644 packages/pino-transport/src/index.ts delete mode 100644 packages/pino-transport/test/index.test.ts delete mode 100644 packages/pino-transport/tsconfig.json delete mode 100644 packages/pino-transport/tsconfig.test.json delete mode 100644 packages/pino-transport/tsconfig.types.json delete mode 100644 packages/pino-transport/vite.config.ts diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 64709dd1f999..8acac6fd2709 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -51,7 +51,6 @@ body: - '@sentry/nestjs' - '@sentry/nextjs' - '@sentry/nuxt' - - '@sentry/pino-transport' - '@sentry/react' - '@sentry/react-router' - '@sentry/remix' diff --git a/dev-packages/e2e-tests/verdaccio-config/config.yaml b/dev-packages/e2e-tests/verdaccio-config/config.yaml index 1d565dbecda2..0773603b033e 100644 --- a/dev-packages/e2e-tests/verdaccio-config/config.yaml +++ b/dev-packages/e2e-tests/verdaccio-config/config.yaml @@ -122,12 +122,6 @@ packages: unpublish: $all # proxy: npmjs # Don't proxy for E2E tests! - '@sentry/pino-transport': - access: $all - publish: $all - unpublish: $all - # proxy: npmjs # Don't proxy for E2E tests! - '@sentry/profiling-node': access: $all publish: $all diff --git a/package.json b/package.json index e74379564683..edbd645b3c97 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,6 @@ "packages/node-native", "packages/nuxt", "packages/opentelemetry", - "packages/pino-transport", "packages/profiling-node", "packages/react", "packages/react-router", diff --git a/packages/pino-transport/.eslintrc.js b/packages/pino-transport/.eslintrc.js deleted file mode 100644 index 01c6be4c7080..000000000000 --- a/packages/pino-transport/.eslintrc.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = { - env: { - node: true, - }, - extends: ['../../.eslintrc.js'], - overrides: [ - { - files: ['src/**/*.ts'], - rules: {}, - }, - ], -}; diff --git a/packages/pino-transport/LICENSE b/packages/pino-transport/LICENSE deleted file mode 100644 index 5251db3eaaca..000000000000 --- a/packages/pino-transport/LICENSE +++ /dev/null @@ -1,16 +0,0 @@ -MIT License - -Copyright (c) 2025 Functional Software, Inc. dba Sentry - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit -persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/pino-transport/README.md b/packages/pino-transport/README.md deleted file mode 100644 index fdc077aa2b01..000000000000 --- a/packages/pino-transport/README.md +++ /dev/null @@ -1,266 +0,0 @@ -

- - Sentry - -

- -# Official Sentry Pino Transport - -[![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) - -**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/pino-transport pino -# or -yarn add @sentry/pino-transport pino -# or -pnpm add @sentry/pino-transport pino -``` - -## Requirements - -- Node.js 18+ -- Pino v8 or v9 -- `@sentry/node` SDK with `enableLogs: true` - -## Setup - -First, make sure Sentry is initialized with logging enabled: - -```javascript -import * as Sentry from '@sentry/node'; - -Sentry.init({ - dsn: 'YOUR_DSN', - 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 - -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 `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 - -- [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 - -MIT diff --git a/packages/pino-transport/package.json b/packages/pino-transport/package.json deleted file mode 100644 index 13c83d19b6ee..000000000000 --- a/packages/pino-transport/package.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "name": "@sentry/pino-transport", - "version": "10.17.0", - "description": "Pino transport for Sentry SDK", - "repository": "git://github.com/getsentry/sentry-javascript.git", - "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/pino-transport", - "author": "Sentry", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "files": [ - "/build" - ], - "main": "build/cjs/index.js", - "module": "build/esm/index.js", - "types": "build/types/index.d.ts", - "exports": { - "./package.json": "./package.json", - ".": { - "import": { - "types": "./build/types/index.d.ts", - "default": "./build/esm/index.js" - }, - "require": { - "types": "./build/types/index.d.ts", - "default": "./build/cjs/index.js" - } - } - }, - "typesVersions": { - "<5.0": { - "build/types/index.d.ts": [ - "build/types-ts3.8/index.d.ts" - ] - } - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@sentry/core": "10.17.0", - "@sentry/node": "10.17.0", - "pino-abstract-transport": "^2.0.0" - }, - "peerDependencies": { - "pino": "^8.0.0 || ^9.0.0" - }, - "devDependencies": { - "@types/node": "^18.19.1", - "pino": "^9.0.0" - }, - "scripts": { - "build": "run-p build:transpile build:types", - "build:dev": "yarn build", - "build:transpile": "rollup -c rollup.npm.config.mjs", - "build:types": "run-s build:types:core build:types:downlevel", - "build:types:core": "tsc -p tsconfig.types.json", - "build:types:downlevel": "yarn downlevel-dts build/types build/types-ts3.8 --to ts3.8", - "build:watch": "run-p build:transpile:watch build:types:watch", - "build:dev:watch": "yarn build:watch", - "build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch", - "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:tarball": "npm pack", - "circularDepCheck": "madge --circular src/index.ts", - "clean": "rimraf build coverage sentry-pino-transport-*.tgz", - "fix": "eslint . --format stylish --fix", - "lint": "eslint . --format stylish", - "lint:es-compatibility": "es-check es2022 ./build/cjs/*.js && es-check es2022 ./build/esm/*.js --module", - "test": "yarn test:unit", - "test:unit": "vitest run", - "test:watch": "vitest --watch", - "yalc:publish": "yalc publish --push --sig" - }, - "volta": { - "extends": "../../package.json" - }, - "sideEffects": false -} diff --git a/packages/pino-transport/rollup.npm.config.mjs b/packages/pino-transport/rollup.npm.config.mjs deleted file mode 100644 index 84a06f2fb64a..000000000000 --- a/packages/pino-transport/rollup.npm.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; - -export default makeNPMConfigVariants(makeBaseNPMConfig()); diff --git a/packages/pino-transport/src/debug-build.ts b/packages/pino-transport/src/debug-build.ts deleted file mode 100644 index 60aa50940582..000000000000 --- a/packages/pino-transport/src/debug-build.ts +++ /dev/null @@ -1,8 +0,0 @@ -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__; diff --git a/packages/pino-transport/src/index.ts b/packages/pino-transport/src/index.ts deleted file mode 100644 index 986c7e892fc2..000000000000 --- a/packages/pino-transport/src/index.ts +++ /dev/null @@ -1,244 +0,0 @@ -import type { LogSeverityLevel } from '@sentry/core'; -import { _INTERNAL_captureLog, debug, isPrimitive, normalize } from '@sentry/core'; -import type buildType from 'pino-abstract-transport'; -import * as pinoAbstractTransport from 'pino-abstract-transport'; -import { DEBUG_BUILD } from './debug-build'; - -// Handle both CommonJS and ES module exports -// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any -const build = (pinoAbstractTransport as any).default || pinoAbstractTransport; - -/** - * The default log levels that will be captured by the Sentry Pino transport. - */ -const DEFAULT_CAPTURED_LEVELS: Array = ['trace', 'debug', 'info', 'warn', 'error', 'fatal']; - -/** - * Options for the Sentry Pino transport. - */ -export interface SentryPinoTransportOptions { - /** - * Use this option to filter which levels should be captured as logs. - * By default, all levels are captured as logs. - * - * @example - * ```ts - * const logger = pino({ - * transport: { - * target: '@sentry/pino-transport', - * options: { - * logLevels: ['error', 'warn'], // Only capture error and warn logs - * }, - * }, - * }); - * ``` - */ - logLevels?: Array; -} - -/** - * Pino source configuration passed to the transport. - * This interface represents the configuration options that Pino provides to transports. - */ -interface PinoSourceConfig { - /** - * Custom levels configuration from Pino. - * Contains the mapping of custom level names to numeric values. - * - * @default undefined - * @example { values: { critical: 55, notice: 35 } } - */ - levels?: unknown; - - /** - * The property name used for the log message. - * Pino allows customizing which property contains the main log message. - * - * @default 'msg' - * @example 'message' when configured with messageKey: 'message' - * @see https://getpino.io/#/docs/api?id=messagekey-string - */ - messageKey?: string; - - /** - * The property name used for error objects. - * Pino allows customizing which property contains error information. - * - * @default 'err' - * @example 'error' when configured with errorKey: 'error' - * @see https://getpino.io/#/docs/api?id=errorkey-string - */ - errorKey?: string; - - /** - * The property name used to nest logged objects to avoid conflicts. - * When set, Pino nests all logged objects under this key to prevent - * conflicts with Pino's internal properties (level, time, pid, etc.). - * The transport flattens these nested properties using dot notation. - * - * @default undefined (no nesting) - * @example 'payload' - objects logged will be nested under { payload: {...} } - * @see https://getpino.io/#/docs/api?id=nestedkey-string - */ - nestedKey?: string; -} - -/** - * Creates a new Sentry Pino transport that forwards logs to Sentry. Requires the `enableLogs` option to be enabled. - * - * Supports Pino v8 and v9. - * - * @param options - Options for the transport. - * @returns A Pino transport that forwards logs to Sentry. - * - * @experimental This method will experience breaking changes. This is not yet part of - * the stable Sentry SDK API and can be changed or removed without warning. - */ -export function createSentryPinoTransport(options?: SentryPinoTransportOptions): ReturnType { - DEBUG_BUILD && debug.log('Initializing Sentry Pino transport'); - const capturedLogLevels = new Set(options?.logLevels ?? DEFAULT_CAPTURED_LEVELS); - - return build( - async function (source: AsyncIterable & PinoSourceConfig) { - for await (const log of source) { - try { - if (!isObject(log)) { - continue; - } - - // Use Pino's messageKey if available, fallback to 'msg' - const messageKey = source.messageKey || 'msg'; - const message = log[messageKey]; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { [messageKey]: _, level, time, ...attributes } = log; - - // Handle nestedKey flattening if configured - if (source.nestedKey && attributes[source.nestedKey] && isObject(attributes[source.nestedKey])) { - const nestedObject = attributes[source.nestedKey] as Record; - // Remove the nested object and flatten its properties - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete attributes[source.nestedKey]; - - // Flatten nested properties with dot notation - for (const [key, value] of Object.entries(nestedObject)) { - attributes[`${source.nestedKey}.${key}`] = value; - } - } - - const logSeverityLevel = mapPinoLevelToSentryLevel(log.level, source.levels); - - if (capturedLogLevels.has(logSeverityLevel)) { - const logAttributes: Record = { - ...attributes, - 'sentry.origin': 'auto.logging.pino', - }; - - // Attach custom level as an attribute if it's a string (custom level) - if (typeof log.level === 'string') { - logAttributes['sentry.pino.level'] = log.level; - } - - _INTERNAL_captureLog({ - level: logSeverityLevel, - message: formatMessage(message), - attributes: logAttributes, - }); - } - } catch { - // Silently ignore errors to prevent breaking the logging pipeline - } - } - }, - { - expectPinoConfig: true, - }, - ); -} - -function formatMessage(message: unknown): string { - if (message === undefined) { - return ''; - } - - if (isPrimitive(message)) { - return String(message); - } - return JSON.stringify(normalize(message)); -} - -/** - * Maps a Pino log level (numeric or custom string) to a Sentry log severity level. - * - * Handles both standard and custom levels, including when `useOnlyCustomLevels` is enabled. - * Uses range-based mapping for numeric levels to handle custom values (e.g., 11 -> trace). - */ -function mapPinoLevelToSentryLevel(level: unknown, levelsConfig?: unknown): LogSeverityLevel { - // Handle numeric levels - if (typeof level === 'number') { - return mapNumericLevelToSentryLevel(level); - } - - // Handle custom string levels - if ( - typeof level === 'string' && - isObject(levelsConfig) && - 'values' in levelsConfig && - isObject(levelsConfig.values) - ) { - // Map custom string levels to numeric then to Sentry levels - const numericLevel = levelsConfig.values[level]; - if (typeof numericLevel === 'number') { - return mapNumericLevelToSentryLevel(numericLevel); - } - } - - // Default fallback - return 'info'; -} - -/** - * Maps a numeric level to the closest Sentry severity level using range-based mapping. - * Handles both standard Pino levels and custom numeric levels. - * - * - `0-19` -> `trace` - * - `20-29` -> `debug` - * - `30-39` -> `info` - * - `40-49` -> `warn` - * - `50-59` -> `error` - * - `60+` -> `fatal` - * - * @see https://github.com/pinojs/pino/blob/116b1b17935630b97222fbfd1c053d199d18ca4b/lib/constants.js#L6-L13 - */ -function mapNumericLevelToSentryLevel(numericLevel: number): LogSeverityLevel { - // 0-19 -> trace - if (numericLevel < 20) { - return 'trace'; - } - // 20-29 -> debug - if (numericLevel < 30) { - return 'debug'; - } - // 30-39 -> info - if (numericLevel < 40) { - return 'info'; - } - // 40-49 -> warn - if (numericLevel < 50) { - return 'warn'; - } - // 50-59 -> error - if (numericLevel < 60) { - return 'error'; - } - // 60+ -> fatal - return 'fatal'; -} - -/** - * Type guard to check if a value is an object. - */ -function isObject(value: unknown): value is Record { - return typeof value === 'object' && value != null; -} - -export default createSentryPinoTransport; diff --git a/packages/pino-transport/test/index.test.ts b/packages/pino-transport/test/index.test.ts deleted file mode 100644 index a93d56f340cd..000000000000 --- a/packages/pino-transport/test/index.test.ts +++ /dev/null @@ -1,708 +0,0 @@ -import { _INTERNAL_captureLog } from '@sentry/core'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { createSentryPinoTransport } from '../src'; - -// Mock the _INTERNAL_captureLog function -vi.mock('@sentry/core', async actual => { - const actualModule = (await actual()) as any; - return { - ...actualModule, - _INTERNAL_captureLog: vi.fn(), - }; -}); - -const mockCaptureLog = vi.mocked(_INTERNAL_captureLog); - -describe('createSentryPinoTransport', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - afterEach(() => { - vi.resetAllMocks(); - }); - - it('should be defined', () => { - expect(createSentryPinoTransport).toBeDefined(); - }); - - it('should create a transport that forwards logs to Sentry', async () => { - const transport = await createSentryPinoTransport(); - expect(transport).toBeDefined(); - expect(typeof transport.write).toBe('function'); - }); - - it('should capture logs with correct level mapping', async () => { - const transport = await createSentryPinoTransport(); - - // Simulate a Pino log entry - const testLog = { - level: 30, // info level in Pino - msg: 'Test message', - time: Date.now(), - hostname: 'test-host', - pid: 12345, - }; - - // Write the log to the transport - transport.write(`${JSON.stringify(testLog)}\n`); - - // Give it a moment to process - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', - message: 'Test message', - attributes: expect.objectContaining({ - hostname: 'test-host', - pid: 12345, - 'sentry.origin': 'auto.logging.pino', - }), - }); - }); - - it('should map all Pino log levels correctly', async () => { - const transport = await createSentryPinoTransport(); - - const testCases = [ - { pinoLevel: 10, expectedSentryLevel: 'trace' }, - { pinoLevel: 20, expectedSentryLevel: 'debug' }, - { pinoLevel: 30, expectedSentryLevel: 'info' }, - { pinoLevel: 40, expectedSentryLevel: 'warn' }, - { pinoLevel: 50, expectedSentryLevel: 'error' }, - { pinoLevel: 60, expectedSentryLevel: 'fatal' }, - ]; - - for (const { pinoLevel, expectedSentryLevel } of testCases) { - const testLog = { - level: pinoLevel, - msg: `Test ${expectedSentryLevel} message`, - time: Date.now(), - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - } - - // Give it a moment to process all logs - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledTimes(6); - - testCases.forEach(({ expectedSentryLevel }, index) => { - expect(mockCaptureLog).toHaveBeenNthCalledWith(index + 1, { - level: expectedSentryLevel, - message: `Test ${expectedSentryLevel} message`, - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - }), - }); - }); - }); - - it('should respect level filtering', async () => { - const transport = await createSentryPinoTransport({ - logLevels: ['error', 'fatal'], - }); - - const testLogs = [ - { level: 30, msg: 'Info message' }, // Should be filtered out - { level: 50, msg: 'Error message' }, // Should be captured - { level: 60, msg: 'Fatal message' }, // Should be captured - ]; - - for (const testLog of testLogs) { - transport.write(`${JSON.stringify(testLog)}\n`); - } - - // Give it a moment to process all logs - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledTimes(2); - expect(mockCaptureLog).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - level: 'error', - message: 'Error message', - }), - ); - expect(mockCaptureLog).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - level: 'fatal', - message: 'Fatal message', - }), - ); - }); - - it('should handle unknown levels gracefully', async () => { - const transport = await createSentryPinoTransport(); - - const testLog = { - level: 999, // Unknown level - msg: 'Unknown level message', - time: Date.now(), - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - - // Give it a moment to process - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'fatal', // 999 maps to fatal (55+ range) - message: 'Unknown level message', - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - }), - }); - }); - - it('should handle non-numeric levels gracefully', async () => { - const transport = await createSentryPinoTransport(); - - const testLog = { - level: 'invalid', // Non-numeric level - msg: 'Invalid level message', - time: Date.now(), - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - - // Give it a moment to process - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', // Default fallback - message: 'Invalid level message', - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - 'sentry.pino.level': 'invalid', - }), - }); - }); - - it('should handle malformed JSON gracefully', async () => { - const transport = await createSentryPinoTransport(); - - // Write invalid JSON - transport.write('{ invalid json \n'); - - // Give it a moment to process - await new Promise(resolve => setTimeout(resolve, 10)); - - // Should not crash and should not call captureLog - expect(mockCaptureLog).not.toHaveBeenCalled(); - }); - - it('should handle non-object logs gracefully', async () => { - const transport = await createSentryPinoTransport(); - - // Write a string instead of an object - transport.write('"just a string"\n'); - - // Give it a moment to process - await new Promise(resolve => setTimeout(resolve, 10)); - - // pino-abstract-transport parses JSON, so this actually becomes an object - // The transport should handle it gracefully by logging it - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', // Default fallback since no level provided - message: '', // Empty string for undefined message - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - }), - }); - }); - - it('should handle string levels gracefully when no custom levels config is available', async () => { - const transport = await createSentryPinoTransport(); - - const testLog = { - level: 'custom', // String level without custom levels config - msg: 'Custom string level message', - time: Date.now(), - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', // Should fallback to info for unknown string levels - message: 'Custom string level message', - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - 'sentry.pino.level': 'custom', - }), - }); - }); - - it('should attach custom level name as attribute for string levels', async () => { - const transport = await createSentryPinoTransport(); - - const testLog = { - level: 'critical', // Custom string level - msg: 'Critical level message', - time: Date.now(), - userId: 123, - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', // Mapped level - message: 'Critical level message', - attributes: expect.objectContaining({ - userId: 123, - 'sentry.origin': 'auto.logging.pino', - 'sentry.pino.level': 'critical', // Original custom level name preserved - }), - }); - }); - - it('should not attach custom level attribute for numeric levels', async () => { - const transport = await createSentryPinoTransport(); - - const testLog = { - level: 30, // Standard numeric level - msg: 'Standard level message', - time: Date.now(), - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', - message: 'Standard level message', - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - // Should NOT have 'sentry.pino.level' for numeric levels - }), - }); - - // Explicitly check that the custom level attribute is not present - const capturedCall = mockCaptureLog.mock.calls[0][0]; - expect(capturedCall.attributes).not.toHaveProperty('sentry.pino.level'); - }); - - it('should handle custom numeric levels with range-based mapping', async () => { - const transport = await createSentryPinoTransport(); - - const testCases = [ - { level: 11, expectedSentryLevel: 'trace' }, // 11 is in trace range (0-14) - { level: 23, expectedSentryLevel: 'debug' }, // 23 is in debug range (15-24) - { level: 33, expectedSentryLevel: 'info' }, // 33 is in info range (25-34) - { level: 42, expectedSentryLevel: 'warn' }, // 42 is in warn range (35-44) - { level: 52, expectedSentryLevel: 'error' }, // 52 is in error range (45-54) - { level: 75, expectedSentryLevel: 'fatal' }, // 75 is in fatal range (55+) - ]; - - for (const { level } of testCases) { - const testLog = { - level, - msg: `Custom numeric level ${level}`, - time: Date.now(), - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - } - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledTimes(6); - - testCases.forEach(({ level, expectedSentryLevel }, index) => { - expect(mockCaptureLog).toHaveBeenNthCalledWith(index + 1, { - level: expectedSentryLevel, - message: `Custom numeric level ${level}`, - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - }), - }); - }); - }); - - it('should handle nested keys', async () => { - const transport = await createSentryPinoTransport(); - - // Test with logs that include a nested object structure as Pino would create - // when nestedKey is configured (we'll test by manually checking the flattening logic) - const testLog = { - level: 30, - msg: 'Test message with nested payload', - time: Date.now(), - payload: { - level: 'hi', // Conflicting with Pino's level - time: 'never', // Conflicting with Pino's time - foo: 'bar', - userId: 123, - }, - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - - await new Promise(resolve => setTimeout(resolve, 10)); - - // Without nestedKey configuration, the nested object should remain as-is - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', - message: 'Test message with nested payload', - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - payload: { - level: 'hi', - time: 'never', - foo: 'bar', - userId: 123, - }, // Should remain nested without nestedKey config - }), - }); - }); - - it('should handle logs without conflicting nested objects', async () => { - const transport = await createSentryPinoTransport(); - - const testLog = { - level: 40, - msg: 'Warning with simple nested data', - time: Date.now(), - data: { - errorCode: 'E001', - module: 'auth', - details: 'Invalid credentials', - }, - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'warn', - message: 'Warning with simple nested data', - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - data: { - errorCode: 'E001', - module: 'auth', - details: 'Invalid credentials', - }, // Should remain as nested object - }), - }); - }); - - it('should handle logs with multiple nested objects', async () => { - const transport = await createSentryPinoTransport(); - - const testLog = { - level: 30, - msg: 'Test message with multiple nested objects', - time: Date.now(), - user: { - id: 123, - name: 'John Doe', - }, - request: { - method: 'POST', - url: '/api/users', - headers: { - 'content-type': 'application/json', - }, - }, - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', - message: 'Test message with multiple nested objects', - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - user: { - id: 123, - name: 'John Doe', - }, - request: { - method: 'POST', - url: '/api/users', - headers: { - 'content-type': 'application/json', - }, - }, - }), - }); - }); - - it('should handle null nested objects', async () => { - const transport = await createSentryPinoTransport(); - - const testLog = { - level: 30, - msg: 'Test message with null values', - time: Date.now(), - data: null, - user: undefined, - config: { - setting: null, - }, - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', - message: 'Test message with null values', - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - data: null, - config: { - setting: null, - }, - }), - }); - }); - - it('should work normally with mixed data types', async () => { - const transport = await createSentryPinoTransport(); - - const testLog = { - level: 30, - msg: 'Mixed data types log', - time: Date.now(), - stringValue: 'test', - numberValue: 42, - booleanValue: true, - arrayValue: [1, 2, 3], - objectValue: { nested: 'value' }, - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', - message: 'Mixed data types log', - attributes: expect.objectContaining({ - stringValue: 'test', - numberValue: 42, - booleanValue: true, - arrayValue: [1, 2, 3], - objectValue: { nested: 'value' }, - 'sentry.origin': 'auto.logging.pino', - }), - }); - }); - - it('should handle string messages', async () => { - const transport = await createSentryPinoTransport(); - - const testLog = { - level: 30, - msg: 'This is a string message', - time: Date.now(), - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', - message: 'This is a string message', - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - }), - }); - }); - - it('should handle number messages', async () => { - const transport = await createSentryPinoTransport(); - - const testLog = { - level: 30, - msg: 42, - time: Date.now(), - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', - message: '42', - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - }), - }); - }); - - it('should handle boolean messages', async () => { - const transport = await createSentryPinoTransport(); - - const testCases = [{ msg: true }, { msg: false }]; - - for (const { msg } of testCases) { - const testLog = { - level: 30, - msg, - time: Date.now(), - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - } - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledTimes(2); - expect(mockCaptureLog).toHaveBeenNthCalledWith(1, { - level: 'info', - message: 'true', - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - }), - }); - expect(mockCaptureLog).toHaveBeenNthCalledWith(2, { - level: 'info', - message: 'false', - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - }), - }); - }); - - it('should handle null and undefined messages', async () => { - const transport = await createSentryPinoTransport(); - - const testCases = [{ msg: null }, { msg: undefined }]; - - for (const { msg } of testCases) { - const testLog = { - level: 30, - msg, - time: Date.now(), - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - } - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledTimes(2); - expect(mockCaptureLog).toHaveBeenNthCalledWith(1, { - level: 'info', - message: 'null', - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - }), - }); - expect(mockCaptureLog).toHaveBeenNthCalledWith(2, { - level: 'info', - message: '', - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - }), - }); - }); - - it('should handle object messages', async () => { - const transport = await createSentryPinoTransport(); - - const testLog = { - level: 30, - msg: { key: 'value', nested: { prop: 123 } }, - time: Date.now(), - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', - message: '{"key":"value","nested":{"prop":123}}', - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - }), - }); - }); - - it('should handle array messages', async () => { - const transport = await createSentryPinoTransport(); - - const testLog = { - level: 30, - msg: [1, 'two', { three: 3 }], - time: Date.now(), - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', - message: '[1,"two",{"three":3}]', - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - }), - }); - }); - - it('should handle circular object messages gracefully', async () => { - const transport = await createSentryPinoTransport(); - - // Create a test log with a circular object as the message - // We can't use JSON.stringify directly, so we'll simulate what happens - const testLog = { - level: 30, - msg: { name: 'test', circular: true }, // Simplified object that represents circular data - time: Date.now(), - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', - message: '{"name":"test","circular":true}', // The object should be serialized normally - attributes: expect.objectContaining({ - 'sentry.origin': 'auto.logging.pino', - }), - }); - }); - - it('should handle missing message gracefully', async () => { - const transport = await createSentryPinoTransport(); - - const testLog = { - level: 30, - // No msg property - time: Date.now(), - someOtherData: 'value', - }; - - transport.write(`${JSON.stringify(testLog)}\n`); - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(mockCaptureLog).toHaveBeenCalledWith({ - level: 'info', - message: '', // Empty string for undefined message - attributes: expect.objectContaining({ - someOtherData: 'value', - 'sentry.origin': 'auto.logging.pino', - }), - }); - }); -}); diff --git a/packages/pino-transport/tsconfig.json b/packages/pino-transport/tsconfig.json deleted file mode 100644 index 64d6f3a1b9e0..000000000000 --- a/packages/pino-transport/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../tsconfig.json", - - "include": ["src/**/*"], - - "compilerOptions": { - "lib": ["es2020"], - "module": "Node16" - } -} diff --git a/packages/pino-transport/tsconfig.test.json b/packages/pino-transport/tsconfig.test.json deleted file mode 100644 index 4c24dbbea96e..000000000000 --- a/packages/pino-transport/tsconfig.test.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["test/**/*", "src/**/*", "vite.config.ts"], - "compilerOptions": { - "types": ["vitest/globals", "node"] - } -} diff --git a/packages/pino-transport/tsconfig.types.json b/packages/pino-transport/tsconfig.types.json deleted file mode 100644 index f35cdd6b5d81..000000000000 --- a/packages/pino-transport/tsconfig.types.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "build/types", - "declaration": true, - "declarationMap": true, - "emitDeclarationOnly": true, - "stripInternal": true - } -} diff --git a/packages/pino-transport/vite.config.ts b/packages/pino-transport/vite.config.ts deleted file mode 100644 index ff64487a9265..000000000000 --- a/packages/pino-transport/vite.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from 'vitest/config'; -import baseConfig from '../../vite/vite.config'; - -export default defineConfig({ - ...baseConfig, - test: { - ...baseConfig.test, - environment: 'node', - }, -}); From b93250a61ce4a2f0b6377fb296bea47296ec33e1 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 2 Oct 2025 10:27:06 -0400 Subject: [PATCH 2/2] oops yarn.lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 2b7fc676accc..4d084a8cf3c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24777,7 +24777,7 @@ pino-std-serializers@^7.0.0: resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz#7c625038b13718dbbd84ab446bd673dc52259e3b" integrity sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA== -pino@9.9.4, pino@^9.0.0: +pino@9.9.4: version "9.9.4" resolved "https://registry.yarnpkg.com/pino/-/pino-9.9.4.tgz#21ed2c27cc177f797e3249c99d340f0bcd6b248e" integrity sha512-d1XorUQ7sSKqVcYdXuEYs2h1LKxejSorMEJ76XoZ0pPDf8VzJMe7GlPXpMBZeQ9gE4ZPIp5uGD+5Nw7scxiigg==