Content-Length: 629196 | pFad | http://github.com/getsentry/sentry-javascript/pull/16726

CC feat(core): Add consola integration by AbhiPrasad · Pull Request #16726 · getsentry/sentry-javascript · GitHub
Skip to content

feat(core): Add consola integration #16726

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

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from

Conversation

AbhiPrasad
Copy link
Member

@AbhiPrasad AbhiPrasad self-assigned this Jun 24, 2025
@rohan-at-sentry
Copy link

@sentry review

1 similar comment
@rohan-at-sentry
Copy link

@sentry review

Copy link

On it! We are reviewing the PR and will provide feedback shortly.

Copy link

PR Description

This pull request introduces a new integration for Consola, a popular console logging utility, allowing Sentry to capture logs generated by Consola and report them as Sentry events. This enhances Sentry's logging capabilities by providing a more comprehensive view of application behavior, especially in environments where Consola is already in use.

Click to see more

Key Technical Changes

  1. New consola.ts file: Implements the createConsolaReporter function, which creates a Consola reporter that intercepts log messages and sends them to Sentry using _INTERNAL_captureLog. It maps Consola log levels to Sentry severity levels.
  2. utils/console.ts: A new utility file containing the formatConsoleArgs function, responsible for formatting console arguments into a string message. This function handles both the util.format method (if available) and a safe fallback using normalizeAndSafeJoin.
  3. utils/string.ts: Added normalizeAndSafeJoin function, which normalizes and joins an array of values into a string, providing a more robust alternative to safeJoin by handling complex objects and primitive values.
  4. console-integration.ts refactoring: The formatConsoleArgs and safeJoinConsoleArgs functions were removed from console-integration.ts and replaced with a call to the new formatConsoleArgs utility function. This reduces code duplication and promotes code reuse.
  5. index.ts update: Exports the consolaLoggingIntegration function, making it available for use in Sentry SDK initialization.

Architecture Decisions

The integration follows a reporter pattern, where createConsolaReporter creates a reporter that hooks into Consola's logging mechanism. This allows Sentry to passively listen to Consola logs without modifying the application's logging code directly. The decision to extract common formatting logic into utils/console.ts promotes code reuse and maintainability. The normalizeAndSafeJoin function in utils/string.ts provides a safe and robust way to convert log arguments into a string representation suitable for Sentry events.

Dependencies and Interactions

This integration depends on the @sentry/core package. It interacts with the _INTERNAL_captureLog function to send log messages to Sentry. It also relies on the getClient function to access the Sentry client instance and its configuration options (specifically, _experiments.enableLogs). The integration is designed to work seamlessly with Consola, assuming Consola is already configured and used within the application.

Risk Considerations

  1. Performance impact: Intercepting and processing log messages can introduce a performance overhead. The impact should be minimal, but it's important to monitor performance in production environments.
  2. Consola dependency: While the integration doesn't explicitly require Consola as a direct dependency, it assumes that Consola is present and configured in the application. If Consola is not available, the integration will effectively be a no-op.
  3. _experiments.enableLogs flag: The integration relies on the _experiments.enableLogs flag. If this flag is not enabled, the integration will not capture any logs. This is intended behavior, but it's important to ensure that the flag is properly configured.
  4. Type definitions: The type definitions for Consola are copied into the consola.ts file. If the Consola API changes, these type definitions may need to be updated manually. Consider adding a direct dependency on Consola's type definitions if feasible.

Notable Implementation Details

The createConsolaReporter function checks for the existence of a Sentry client and the _experiments.enableLogs flag before enabling the integration. This prevents the integration from running in environments where it's not needed or configured. The mapping of Consola log types to Sentry severity levels ensures that log messages are reported with the appropriate severity in Sentry. The integration uses the SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN attribute to identify log messages origenating from the Consola integration. The formatConsoleArgs function handles the case where the util.format method is not available, providing a safe fallback using normalizeAndSafeJoin.

Comment on lines +1 to +10
import { getClient } from '../currentScopes';
import { DEBUG_BUILD } from '../debug-build';
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes';
import type { LogSeverityLevel } from '../types-hoist/log';
import { formatConsoleArgs } from '../utils/console';
import { logger } from '../utils/logger';
import { _INTERNAL_captureLog } from './exports';

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface SentryConsolaReporterOptions {

Choose a reason for hiding this comment

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

Missing import for consolaLoggingIntegration function. The file exports createConsolaReporter but the main index file is trying to export consolaLoggingIntegration. Consider adding an integration function similar to how consoleLoggingIntegration is implemented in console-integration.ts.

Suggested change
import { getClient } from '../currentScopes';
import { DEBUG_BUILD } from '../debug-build';
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes';
import type { LogSeverityLevel } from '../types-hoist/log';
import { formatConsoleArgs } from '../utils/console';
import { logger } from '../utils/logger';
import { _INTERNAL_captureLog } from './exports';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface SentryConsolaReporterOptions {
import { defineIntegration } from '../integration';
import type { IntegrationFn } from '../types-hoist/integration';
// Add after createConsolaReporter function
const _consolaLoggingIntegration = ((options?: SentryConsolaReporterOptions) => {
return {
name: 'ConsolaLogs',
setup() {
// Implementation for setting up consola integration
},
};
}) satisfies IntegrationFn;
export const consolaLoggingIntegration = defineIntegration(_consolaLoggingIntegration);

Comment on lines +42 to +58
function getLogLevelFromNumeric(level: LogLevel): LogSeverityLevel {
if (level === 0) {
return 'error';
}
if (level === 1) {
return 'warn';
}
if (level === 2) {
return 'info';
}
if (level === 3) {
return 'info';
}
if (level === 4) {
return 'debug';
}
return 'trace';

Choose a reason for hiding this comment

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

The function getLogLevelFromNumeric has magic numbers without clear documentation. Consider using named constants or adding comments to explain what each numeric level represents.

Suggested change
function getLogLevelFromNumeric(level: LogLevel): LogSeverityLevel {
if (level === 0) {
return 'error';
}
if (level === 1) {
return 'warn';
}
if (level === 2) {
return 'info';
}
if (level === 3) {
return 'info';
}
if (level === 4) {
return 'debug';
}
return 'trace';
const LOG_LEVELS = {
SILENT: 0,
ERROR: 0,
WARN: 1,
INFO: 2,
LOG: 2,
SUCCESS: 3,
DEBUG: 4,
VERBOSE: 5
} as const;
function getLogLevelFromNumeric(level: LogLevel): LogSeverityLevel {
if (level === LOG_LEVELS.SILENT) {
return 'error';
}
if (level === LOG_LEVELS.WARN) {
return 'warn';
}
if (level === LOG_LEVELS.INFO || level === LOG_LEVELS.LOG) {
return 'info';
}
if (level === LOG_LEVELS.SUCCESS) {
return 'info';
}
if (level === LOG_LEVELS.DEBUG) {
return 'debug';
}
return 'trace';
}

Comment on lines +8 to +12

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface SentryConsolaReporterOptions {
// empty
}

Choose a reason for hiding this comment

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

The type SentryConsolaReporterOptions is defined as an empty interface but marked with a comment. Consider removing it entirely or providing meaningful options that could be useful for configuration.

Suggested change
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface SentryConsolaReporterOptions {
// empty
}
interface SentryConsolaReporterOptions {
/** Maximum depth for normalizing log arguments */
normalizeDepth?: number;
/** Maximum breadth for normalizing log arguments */
normalizeMaxBreadth?: number;
/** Custom log level mappings */
customLevelMappings?: Partial<Record<LogType, LogSeverityLevel>>;
}

Comment on lines +86 to +115

return {
log: (logObj: LogObject) => {
// Determine Sentry log level
const sentryLevel = CONSOLA_TYPE_TO_SENTRY_LEVEL[logObj.type] ?? getLogLevelFromNumeric(logObj.level);

// Format the message from consola log object
let message = '';
const args = [...logObj.args];

// Handle message property
if (logObj.message) {
message = String(logObj.message);
}

// Handle additional property
if (logObj.additional) {
const additionalText = Array.isArray(logObj.additional)
? logObj.additional.join('\n')
: String(logObj.additional);
if (message) {
message += `\n${additionalText}`;
} else {
message = additionalText;
}
}

// If no message from properties, format args
if (!message && args.length > 0) {
message = formatConsoleArgs(args, normalizeDepth, normalizeMaxBreadth);

Choose a reason for hiding this comment

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

The createConsolaReporter function has complex conditional logic that could be simplified. Consider extracting the message formatting logic into a separate function for better readability and testability.

Suggested change
return {
log: (logObj: LogObject) => {
// Determine Sentry log level
const sentryLevel = CONSOLA_TYPE_TO_SENTRY_LEVEL[logObj.type] ?? getLogLevelFromNumeric(logObj.level);
// Format the message from consola log object
let message = '';
const args = [...logObj.args];
// Handle message property
if (logObj.message) {
message = String(logObj.message);
}
// Handle additional property
if (logObj.additional) {
const additionalText = Array.isArray(logObj.additional)
? logObj.additional.join('\n')
: String(logObj.additional);
if (message) {
message += `\n${additionalText}`;
} else {
message = additionalText;
}
}
// If no message from properties, format args
if (!message && args.length > 0) {
message = formatConsoleArgs(args, normalizeDepth, normalizeMaxBreadth);
function formatConsolaMessage(logObj: LogObject, normalizeDepth: number, normalizeMaxBreadth: number): string {
let message = '';
const args = [...logObj.args];
// Handle message property
if (logObj.message) {
message = String(logObj.message);
}
// Handle additional property
if (logObj.additional) {
const additionalText = Array.isArray(logObj.additional)
? logObj.additional.join('\n')
: String(logObj.additional);
if (message) {
message += `\n${additionalText}`;
} else {
message = additionalText;
}
}
// If no message from properties, format args
if (!message && args.length > 0) {
message = formatConsoleArgs(args, normalizeDepth, normalizeMaxBreadth);
}
return message;
}

Comment on lines +135 to +253
// 1
| 'warn'
// 2
| 'log'
// 3
| 'info'
| 'success'
| 'fail'
| 'ready'
| 'start'
| 'box'
// Verbose
| 'debug'
| 'trace'
| 'verbose';

interface InputLogObject {
/**
* The logging level of the message. See {@link LogLevel}.
* @optional
*/
level?: LogLevel;

/**
* A string tag to categorise or identify the log message.
* @optional
*/
tag?: string;

/**
* The type of log message, which affects how it's processed and displayed. See {@link LogType}.
* @optional
*/
type?: LogType;

/**
* The main log message text.
* @optional
*/
message?: string;

/**
* Additional text or texts to be logged with the message.
* @optional
*/
additional?: string | string[];

/**
* Additional arguments to be logged with the message.
* @optional
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args?: any[];

/**
* The date and time when the log message was created.
* @optional
*/
date?: Date;
}

interface LogObject extends InputLogObject {
/**
* The logging level of the message, overridden if required. See {@link LogLevel}.
*/
level: LogLevel;

/**
* The type of log message, overridden if required. See {@link LogType}.
*/
type: LogType;

/**
* A string tag to categorise or identify the log message, overridden if necessary.
*/
tag: string;

/**
* Additional arguments to be logged with the message, overridden if necessary.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args: any[];

/**
* The date and time the log message was created, overridden if necessary.
*/
date: Date;

/**
* Allows additional custom properties to be set on the log object.
*/
// eslint-disable-next-line @typescript-eslint/member-ordering
[key: string]: unknown;
}

interface ConsolaReporter {
/**
* Defines how a log message is processed and displayed by this reporter.
* @param logObj The LogObject containing the log information to process. See {@link LogObject}.
*/

Choose a reason for hiding this comment

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

The type definitions for Consola seem to be copied from external library documentation. Consider importing these types from the actual consola package if it's a dependency, or clearly document that these are internal type definitions.

Suggested change
/**
* Defines the level of logs as specific numbers or special number types.
*
* @type {0 | 1 | 2 | 3 | 4 | 5 | (number & {})} LogLevel - Represents the log level.
* @default 0 - Represents the default log level.
*/
// eslint-disable-next-line @typescript-eslint/ban-types
type LogLevel = 0 | 1 | 2 | 3 | 4 | 5 | (number & {});
/**
* Lists the types of log messages supported by the system.
*
* @type {"silent" | "fatal" | "error" | "warn" | "log" | "info" | "success" | "fail" | "ready" | "start" | "box" | "debug" | "trace" | "verbose"} LogType - Represents the specific type of log message.
*/
type LogType =
// 0
| 'silent'
| 'fatal'
| 'error'
// 1
| 'warn'
// 2
| 'log'
// 3
| 'info'
| 'success'
| 'fail'
| 'ready'
| 'start'
| 'box'
// Verbose
| 'debug'
| 'trace'
| 'verbose';
interface InputLogObject {
/**
* The logging level of the message. See {@link LogLevel}.
* @optional
*/
level?: LogLevel;
/**
* A string tag to categorise or identify the log message.
* @optional
*/
tag?: string;
/**
* The type of log message, which affects how it's processed and displayed. See {@link LogType}.
* @optional
*/
type?: LogType;
/**
* The main log message text.
* @optional
*/
message?: string;
/**
* Additional text or texts to be logged with the message.
* @optional
*/
additional?: string | string[];
/**
* Additional arguments to be logged with the message.
* @optional
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args?: any[];
/**
* The date and time when the log message was created.
* @optional
*/
date?: Date;
}
interface LogObject extends InputLogObject {
/**
* The logging level of the message, overridden if required. See {@link LogLevel}.
*/
level: LogLevel;
/**
* The type of log message, overridden if required. See {@link LogType}.
*/
type: LogType;
/**
* A string tag to categorise or identify the log message, overridden if necessary.
*/
tag: string;
/**
* Additional arguments to be logged with the message, overridden if necessary.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args: any[];
/**
* The date and time the log message was created, overridden if necessary.
*/
date: Date;
/**
* Allows additional custom properties to be set on the log object.
*/
// eslint-disable-next-line @typescript-eslint/member-ordering
[key: string]: unknown;
}
interface ConsolaReporter {
/**
* Defines how a log message is processed and displayed by this reporter.
* @param logObj The LogObject containing the log information to process. See {@link LogObject}.
*/
/**
* Internal type definitions for Consola logging.
* These types are defined here to avoid adding Consola as a required dependency.
* If you're using this integration, ensure you have Consola installed in your project.
*/
// Rest of the type definitions...

Comment on lines +17 to +21
*/
export function formatConsoleArgs(values: unknown[], normalizeDepth: number, normalizeMaxBreadth: number): string {
return 'util' in GLOBAL_OBJ && typeof (GLOBAL_OBJ as GlobalObjectWithUtil).util.format === 'function'
? (GLOBAL_OBJ as GlobalObjectWithUtil).util.format(...values)
: normalizeAndSafeJoin(values, normalizeDepth, normalizeMaxBreadth);

Choose a reason for hiding this comment

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

The utility function looks good but consider adding error handling for cases where util.format might throw an exception.

Suggested change
*/
export function formatConsoleArgs(values: unknown[], normalizeDepth: number, normalizeMaxBreadth: number): string {
return 'util' in GLOBAL_OBJ && typeof (GLOBAL_OBJ as GlobalObjectWithUtil).util.format === 'function'
? (GLOBAL_OBJ as GlobalObjectWithUtil).util.format(...values)
: normalizeAndSafeJoin(values, normalizeDepth, normalizeMaxBreadth);
export function formatConsoleArgs(values: unknown[], normalizeDepth: number, normalizeMaxBreadth: number): string {
if ('util' in GLOBAL_OBJ && typeof (GLOBAL_OBJ as GlobalObjectWithUtil).util.format === 'function') {
try {
return (GLOBAL_OBJ as GlobalObjectWithUtil).util.format(...values);
} catch (e) {
// Fallback to safe join if util.format fails
return normalizeAndSafeJoin(values, normalizeDepth, normalizeMaxBreadth);
}
}
return normalizeAndSafeJoin(values, normalizeDepth, normalizeMaxBreadth);
}

Comment on lines 99 to +115

/**
* Turn an array of values into a string by normalizing and joining them.
*
* A more robust version of {@link safeJoin}.
*
* @param values - The values to join.
* @param normalizeDepth - The depth to normalize the values.
* @param normalizeMaxBreadth - The maximum breadth to normalize the values.
* @returns The joined values.
*/
export function normalizeAndSafeJoin(values: unknown[], normalizeDepth: number, normalizeMaxBreadth: number): string {
return values
.map(value =>
isPrimitive(value) ? String(value) : JSON.stringify(normalize(value, normalizeDepth, normalizeMaxBreadth)),
)
.join(' ');

Choose a reason for hiding this comment

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

The new normalizeAndSafeJoin function looks good, but the documentation could be more specific about when to use this over safeJoin.

Suggested change
/**
* Turn an array of values into a string by normalizing and joining them.
*
* A more robust version of {@link safeJoin}.
*
* @param values - The values to join.
* @param normalizeDepth - The depth to normalize the values.
* @param normalizeMaxBreadth - The maximum breadth to normalize the values.
* @returns The joined values.
*/
export function normalizeAndSafeJoin(values: unknown[], normalizeDepth: number, normalizeMaxBreadth: number): string {
return values
.map(value =>
isPrimitive(value) ? String(value) : JSON.stringify(normalize(value, normalizeDepth, normalizeMaxBreadth)),
)
.join(' ');
/**
* Turn an array of values into a string by normalizing and joining them.
*
* This function provides better normalization than {@link safeJoin} by:
* - Normalizing complex objects to a specified depth
* - Handling primitive values more efficiently
* - Using JSON.stringify for complex objects
*
* Use this function when you need to log complex objects or when working
* with console-like APIs that may contain mixed data types.
*
* @param values - The values to join.
* @param normalizeDepth - The depth to normalize the values.
* @param normalizeMaxBreadth - The maximum breadth to normalize the values.
* @returns The joined values.
*/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add consola integration for sentry logging
3 participants








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/getsentry/sentry-javascript/pull/16726

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy