Skip to main content
Glama
sentry.js8.24 kB
import { createHash } from 'crypto'; /** * Sentry initialization and configuration for Task Master * Provides error tracking and AI operation monitoring */ import * as Sentry from '@sentry/node'; import { getAnonymousTelemetryEnabled, setSuppressConfigWarnings } from '../../scripts/modules/config-manager.js'; let isInitialized = false; /** * Create a privacy-safe hash of a project root path * Uses SHA256 and truncates to 8 characters for grouping without exposing full paths * @param {string} projectRoot - The project root path * @returns {string|undefined} Short hash of the project root, or undefined if no path provided */ export function hashProjectRoot(projectRoot) { if (!projectRoot) return undefined; // Create SHA256 hash and take first 8 characters for grouping return createHash('sha256').update(projectRoot).digest('hex').substring(0, 8); } /** * Initialize Sentry with AI telemetry integration * @param {object} options - Initialization options * @param {string} [options.dsn] - Sentry DSN (defaults to env var SENTRY_DSN) * @param {string} [options.environment] - Environment name (development, production, etc.) * @param {number} [options.tracesSampleRate] - Traces sample rate (0.0 to 1.0) * @param {boolean} [options.sendDefaultPii] - Whether to send PII data * @param {object} [options.session] - MCP session for env resolution * @param {string} [options.projectRoot] - Project root for .env file resolution */ export function initializeSentry(options = {}) { // Avoid double initialization if (isInitialized) { return; } // Check if user has opted out of anonymous telemetry // This applies to local storage users only // Hamster users don't use local config (API storage), so this check doesn't affect them // Suppress config warnings during this check to avoid noisy output at startup setSuppressConfigWarnings(true); try { const telemetryEnabled = getAnonymousTelemetryEnabled(options.projectRoot); if (!telemetryEnabled) { console.log( '✓ Anonymous telemetry disabled per user preference. ' + 'Set anonymousTelemetry: true in .taskmaster/config.json to re-enable.' ); return; } } catch (error) { // If there's an error checking telemetry preferences (e.g., config not available yet), // default to enabled. This ensures telemetry works during initialization. } finally { setSuppressConfigWarnings(false); } // Use internal Sentry DSN for Task Master telemetry // This is a public client-side DSN and is safe to hardcode const dsn = options.dsn || 'https://ce8c03ca1dd0da5b9837c6ba1b3a0f9d@o4510099843776512.ingest.us.sentry.io/4510381945585664'; // DSN is always available, but check if user has opted out if (!dsn) { return; } try { Sentry.init({ dsn, environment: options.environment || process.env.NODE_ENV || 'production', integrations: [ // Add the Vercel AI SDK integration for automatic AI operation tracking Sentry.vercelAIIntegration({ recordInputs: true, recordOutputs: true }), // Add Zod error tracking for better validation error reporting Sentry.zodErrorsIntegration() ], // Tracing must be enabled for AI monitoring to work tracesSampleRate: options.tracesSampleRate ?? 1.0, sendDefaultPii: options.sendDefaultPii ?? true, // Enable debug mode with SENTRY_DEBUG=true env var debug: process.env.SENTRY_DEBUG === 'true' }); isInitialized = true; if (process.env.SENTRY_DEBUG === 'true') { console.log(` DSN: ${dsn.substring(0, 40)}...`); console.log( ` Environment: ${options.environment || process.env.NODE_ENV || 'production'}` ); console.log(` Traces Sample Rate: ${options.tracesSampleRate ?? 1.0}`); } } catch (error) { console.error(`Failed to initialize telemetry: ${error.message}`); } } /** * Get the experimental telemetry configuration for AI SDK calls * Only returns telemetry config if Sentry is initialized * @param {string} [functionId] - Optional function identifier to help correlate spans with function calls * @param {object} [metadata] - Optional metadata to include in telemetry spans * @param {string} [metadata.command] - Command name (e.g., 'add-task', 'update-task') * @param {string} [metadata.outputType] - Output type: 'cli' or 'mcp' * @param {string} [metadata.tag] - Task tag being operated on * @param {string} [metadata.taskId] - Specific task ID if applicable * @param {string} [metadata.userId] - Hamster user ID if authenticated * @param {string} [metadata.briefId] - Hamster brief ID if connected * @param {string} [metadata.projectHash] - Privacy-safe hash of project root * @returns {object|null} Telemetry configuration or null if Sentry not initialized */ export function getAITelemetryConfig(functionId, metadata = {}) { if (!isInitialized) { if (process.env.SENTRY_DEBUG === 'true') { console.log('⚠️ Sentry not initialized, telemetry config not available'); } return null; } const config = { isEnabled: true, recordInputs: true, recordOutputs: true }; // Add functionId if provided - helps correlate captured spans with function calls if (functionId) { config.functionId = functionId; } // Add custom metadata for better filtering and grouping in Sentry // Only include defined metadata fields to avoid clutter if (Object.keys(metadata).length > 0) { config.metadata = {}; if (metadata.command) config.metadata.command = metadata.command; if (metadata.outputType) config.metadata.outputType = metadata.outputType; if (metadata.tag) config.metadata.tag = metadata.tag; if (metadata.taskId) config.metadata.taskId = metadata.taskId; if (metadata.userId) config.metadata.userId = metadata.userId; if (metadata.briefId) config.metadata.briefId = metadata.briefId; if (metadata.projectHash) config.metadata.projectHash = metadata.projectHash; } if (process.env.SENTRY_DEBUG === 'true') { console.log( '📊 Sentry telemetry config created:', JSON.stringify(config, null, 2) ); } return config; } /** * Check if Sentry is initialized * @returns {boolean} True if Sentry is initialized */ export function isSentryInitialized() { return isInitialized; } /** * Flush all pending Sentry events * Critical for short-lived processes like CLI commands * @param {number} [timeout=2000] - Maximum time to wait for events to flush (ms) * @returns {Promise<boolean>} True if flush was successful */ export async function flushSentry(timeout = 2000) { if (!isInitialized) { return false; } try { if (process.env.SENTRY_DEBUG === 'true') { console.log('🔄 Flushing Sentry events...'); } await Sentry.flush(timeout); if (process.env.SENTRY_DEBUG === 'true') { console.log('✓ Sentry events flushed successfully'); } return true; } catch (error) { console.error(`Failed to flush Sentry events: ${error.message}`); return false; } } /** * Capture an exception with Sentry * @param {Error} error - The error to capture * @param {object} [context] - Additional context data */ export function captureException(error, context = {}) { if (!isInitialized) { return; } Sentry.captureException(error, { extra: context }); } /** * Capture a message with Sentry * @param {string} message - The message to capture * @param {string} [level] - Severity level (fatal, error, warning, log, info, debug) * @param {object} [context] - Additional context data */ export function captureMessage(message, level = 'info', context = {}) { if (!isInitialized) { return; } Sentry.captureMessage(message, { level, extra: context }); } /** * Set user context for Sentry events * @param {object} user - User information * @param {string} [user.id] - User ID * @param {string} [user.email] - User email * @param {string} [user.username] - Username */ export function setUser(user) { if (!isInitialized) { return; } Sentry.setUser(user); } /** * Add tags to Sentry events * @param {object} tags - Tags to add */ export function setTags(tags) { if (!isInitialized) { return; } Sentry.setTags(tags); } /** * Reset Sentry initialization state (useful for testing) * @private */ export function _resetSentry() { isInitialized = false; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/eyaltoledano/claude-task-master'

If you have feedback or need assistance with the MCP directory API, please join our Discord server