-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
base: develop
Are you sure you want to change the base?
feat(core): Add consola integration #16726
Conversation
@sentry review |
1 similar comment
@sentry review |
On it! We are reviewing the PR and will provide feedback shortly. |
PR DescriptionThis 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 moreKey Technical Changes
Architecture DecisionsThe integration follows a reporter pattern, where Dependencies and InteractionsThis integration depends on the Risk Considerations
Notable Implementation DetailsThe |
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 { |
There was a problem hiding this comment.
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
.
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); |
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'; |
There was a problem hiding this comment.
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.
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'; | |
} |
|
||
// eslint-disable-next-line @typescript-eslint/no-empty-interface | ||
interface SentryConsolaReporterOptions { | ||
// empty | ||
} |
There was a problem hiding this comment.
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.
// 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>>; | |
} |
|
||
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); |
There was a problem hiding this comment.
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.
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; | |
} |
// 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}. | ||
*/ |
There was a problem hiding this comment.
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.
/** | |
* 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... |
*/ | ||
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); |
There was a problem hiding this comment.
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.
*/ | |
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); | |
} |
|
||
/** | ||
* 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(' '); |
There was a problem hiding this comment.
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
.
/** | |
* 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. | |
*/ |
resolves #16659
https://www.npmjs.com/package/consola