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
Draft
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
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export { captureFeedback } from './feedback';
export type { ReportDialogOptions } from './report-dialog';
export { _INTERNAL_captureLog, _INTERNAL_flushLogsBuffer, _INTERNAL_captureSerializedLog } from './logs/exports';
export { consoleLoggingIntegration } from './logs/console-integration';
export { consolaLoggingIntegration } from './logs/consola';

export type { FeatureFlag } from './utils/featureFlags';
export {
Expand Down
255 changes: 255 additions & 0 deletions packages/core/src/logs/consola.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
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 {
Comment on lines +1 to +10

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);

// empty
}
Comment on lines +8 to +12

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>>;
}


/**
* Map consola log types to Sentry log levels
*/
const CONSOLA_TYPE_TO_SENTRY_LEVEL: Record<LogType, LogSeverityLevel> = {
// 0
silent: 'fatal',
fatal: 'fatal',
error: 'error',
// 1
warn: 'warn',
// 2
log: 'info',
// 3
info: 'info',
success: 'info',
fail: 'info',
ready: 'info',
start: 'info',
box: 'info',
// Verbose
debug: 'debug',
trace: 'trace',
verbose: 'trace',
};

/**
* Map consola log levels (numeric) to Sentry levels
*/
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';
Comment on lines +42 to +58

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';
}

}

/**
* Sentry reporter for Consola. Requires `_experiments.enableLogs` to be enabled.
*
* @experimental This feature is experimental and may be changed or removed in future versions.
*/
export function createConsolaReporter(options?: SentryConsolaReporterOptions, client = getClient()): ConsolaReporter {
if (!client) {
DEBUG_BUILD && logger.warn('No Sentry client found, Consola reporter disabled');
return {
log: () => {
// no-op
},
};
}

const { _experiments, normalizeDepth = 3, normalizeMaxBreadth = 1_000 } = client.getOptions();

if (!_experiments?.enableLogs) {
DEBUG_BUILD && logger.warn('Consola reporter disabled, _experiments.enableLogs is not enabled');
return {
log: () => {
// no-op
},
};
}

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);
Comment on lines +86 to +115

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;
}

}

// Build attributes
const attributes: Record<string, string> = {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.consola.logging',
};
if (logObj.tag) {
attributes['consola.tag'] = logObj.tag;
}

_INTERNAL_captureLog({
level: sentryLevel,
message,
attributes,
});
},
};
}

/**
* 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}.
*/
Comment on lines +135 to +253

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...

log: (logObj: LogObject) => void;
}
24 changes: 1 addition & 23 deletions packages/core/src/logs/console-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,14 @@ import { defineIntegration } from '../integration';
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes';
import type { ConsoleLevel } from '../types-hoist/instrument';
import type { IntegrationFn } from '../types-hoist/integration';
import { isPrimitive } from '../utils/is';
import { formatConsoleArgs } from '../utils/console';
import { CONSOLE_LEVELS, logger } from '../utils/logger';
import { normalize } from '../utils/normalize';
import { GLOBAL_OBJ } from '../utils/worldwide';
import { _INTERNAL_captureLog } from './exports';

interface CaptureConsoleOptions {
levels: ConsoleLevel[];
}

type GlobalObjectWithUtil = typeof GLOBAL_OBJ & {
util: {
format: (...args: unknown[]) => string;
};
};

const INTEGRATION_NAME = 'ConsoleLogs';

const DEFAULT_ATTRIBUTES = {
Expand Down Expand Up @@ -88,17 +80,3 @@ const _consoleLoggingIntegration = ((options: Partial<CaptureConsoleOptions> = {
* ```
*/
export const consoleLoggingIntegration = defineIntegration(_consoleLoggingIntegration);

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)
: safeJoinConsoleArgs(values, normalizeDepth, normalizeMaxBreadth);
}

function safeJoinConsoleArgs(values: unknown[], normalizeDepth: number, normalizeMaxBreadth: number): string {
return values
.map(value =>
isPrimitive(value) ? String(value) : JSON.stringify(normalize(value, normalizeDepth, normalizeMaxBreadth)),
)
.join(' ');
}
22 changes: 22 additions & 0 deletions packages/core/src/utils/console.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { normalizeAndSafeJoin } from './string';
import { GLOBAL_OBJ } from './worldwide';

type GlobalObjectWithUtil = typeof GLOBAL_OBJ & {
util: {
format: (...args: unknown[]) => string;
};
};

/**
* Format console arguments.
*
* @param values - The values to format.
* @param normalizeDepth - The depth to normalize the values.
* @param normalizeMaxBreadth - The maximum breadth to normalize the values.
* @returns The formatted values.
*/
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);
Comment on lines +17 to +21

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);
}

}
26 changes: 24 additions & 2 deletions packages/core/src/utils/string.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { isRegExp, isString, isVueViewModel } from './is';
import { isPrimitive, isRegExp, isString, isVueViewModel } from './is';
import { normalize } from './normalize';

export { escapeStringForRegex } from '../vendor/escapeStringForRegex';

Expand Down Expand Up @@ -60,7 +61,10 @@ export function snipLine(line: string, colno: number): string {
}

/**
* Join values in array
* Join values in array.
*
* We recommend using {@link normalizeAndSafeJoin} instead.
*
* @param input array of values to be joined together
* @param delimiter string to be placed in-between values
* @returns Joined values
Expand Down Expand Up @@ -93,6 +97,24 @@ export function safeJoin(input: unknown[], delimiter?: string): string {
return output.join(delimiter);
}

/**
* 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(' ');
}
Comment on lines 99 to +115

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.
*/


/**
* Checks if the given value matches a regex or string
*
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy