diff --git a/docs/concepts/key-terms/tracing/span-metrics.mdx b/docs/concepts/key-terms/tracing/span-metrics.mdx index 8b4ad8f63da5f..237246e0259d7 100644 --- a/docs/concepts/key-terms/tracing/span-metrics.mdx +++ b/docs/concepts/key-terms/tracing/span-metrics.mdx @@ -20,22 +20,17 @@ By attaching attributes directly to spans, you create a unified view of both the // Simple database query with dynamic attributes Sentry.startSpan( { - name: 'Database Query', - op: 'db.query' + name: "Database Query", + op: "db.query", }, - () => { - // Get active span to set attributes as data becomes available - const span = Sentry.getActiveSpan(); - + (span) => { // Execute query and add results to span - const result = executeQuery('SELECT * FROM users WHERE active = true'); - + const result = executeQuery("SELECT * FROM users WHERE active = true"); + // Set attributes with the results data - if (span) { - span.setAttribute('db.rows_returned', result.length); - span.setAttribute('db.execution_time_ms', result.executionTime); - } - + span.setAttribute("db.rows_returned", result.length); + span.setAttribute("db.execution_time_ms", result.executionTime); + return result; } ); @@ -55,14 +50,14 @@ By integrating these attributes into tracing, you can maintain a single telemetr // Adding business context to a payment processing span Sentry.startSpan( { - name: 'Process Payment', - op: 'payment.process', + name: "Process Payment", + op: "payment.process", attributes: { - 'payment.amount': 99.99, - 'payment.currency': 'USD', - 'payment.method': 'credit_card', - 'customer.type': 'returning' - } + "payment.amount": 99.99, + "payment.currency": "USD", + "payment.method": "credit_card", + "customer.type": "returning", + }, }, async () => { // Payment processing implementation @@ -91,12 +86,12 @@ Augment automatically-created or manually-defined spans with additional attribut const span = Sentry.getActiveSpan(); if (span) { // User context - span.setAttribute('user.subscription_tier', 'premium'); - + span.setAttribute("user.subscription_tier", "premium"); + // Multiple metrics in a single operation span.setAttributes({ - 'memory.heap_used': 1024000, - 'processing.total_steps': 5 + "memory.heap_used": 1024000, + "processing.total_steps": 5, }); } ``` @@ -109,16 +104,16 @@ Create spans specifically for grouping related attributes or metrics, particular // Creating a span for monitoring external API usage Sentry.startSpan( { - name: 'Third-Party API Usage', - op: 'external.api', + name: "Third-Party API Usage", + op: "external.api", attributes: { // Performance metrics - 'api.response_time_ms': 245, - + "api.response_time_ms": 245, + // Context data - 'feature.using_api': 'image_recognition', - 'user.plan': 'enterprise' - } + "feature.using_api": "image_recognition", + "user.plan": "enterprise", + }, }, async () => { // API call implementation @@ -138,15 +133,15 @@ Monitor client-side performance metrics related to user experience: // UI performance monitoring Sentry.startSpan( { - name: 'Page Interaction', - op: 'ui.interaction', + name: "Page Interaction", + op: "ui.interaction", attributes: { - 'ui.first_input_delay_ms': 24, - 'ui.time_to_interactive_ms': 320, - 'ui.frames_dropped': 0, - 'user.device_type': 'mobile', - 'feature.being_used': 'image_carousel' - } + "ui.first_input_delay_ms": 24, + "ui.time_to_interactive_ms": 320, + "ui.frames_dropped": 0, + "user.device_type": "mobile", + "feature.being_used": "image_carousel", + }, }, async () => { // UI interaction handling @@ -162,17 +157,17 @@ Track database performance characteristics and their impact on application behav // Database query monitoring Sentry.startSpan( { - name: 'Product Search Query', - op: 'db.query', + name: "Product Search Query", + op: "db.query", attributes: { - 'db.query_type': 'SELECT', - 'db.table': 'products', - 'db.execution_time_ms': 145, - 'db.rows_returned': 87, - 'db.index_used': 'product_category_idx', - 'business.search_term': 'winter jacket', - 'business.search_filters_applied': 3 - } + "db.query_type": "SELECT", + "db.table": "products", + "db.execution_time_ms": 145, + "db.rows_returned": 87, + "db.index_used": "product_category_idx", + "business.search_term": "winter jacket", + "business.search_filters_applied": 3, + }, }, async () => { // Database query execution @@ -188,21 +183,21 @@ Monitor file handling operations across your application stack: // File processing monitoring Sentry.startSpan( { - name: 'Process Uploaded Image', - op: 'file.process', + name: "Process Uploaded Image", + op: "file.process", attributes: { // Technical metrics - 'file.size_bytes': 2500000, - 'file.type': 'image/jpeg', - + "file.size_bytes": 2500000, + "file.type": "image/jpeg", + // Processing metrics - 'processing.steps_completed': ['virus_scan', 'resize', 'compress'], - 'processing.total_time_ms': 850, - + "processing.steps_completed": ["virus_scan", "resize", "compress"], + "processing.total_time_ms": 850, + // Context data - 'feature.using_upload': 'user_avatar', - 'subscription.allows_hd': true - } + "feature.using_upload": "user_avatar", + "subscription.allows_hd": true, + }, }, async () => { // Image processing implementation @@ -218,21 +213,21 @@ Monitor performance and reliability of third-party service interactions: // Payment gateway monitoring Sentry.startSpan( { - name: 'Payment Gateway', - op: 'payment.gateway', + name: "Payment Gateway", + op: "payment.gateway", attributes: { // Performance metrics - 'gateway.response_time_ms': 980, - 'gateway.retry_count': 0, - + "gateway.response_time_ms": 980, + "gateway.retry_count": 0, + // Transaction data - 'order.total_amount': 159.99, - 'order.items_count': 3, - + "order.total_amount": 159.99, + "order.items_count": 3, + // Service metrics - 'gateway.fee_amount': 4.50, - 'gateway.fee_percent': 0.029 - } + "gateway.fee_amount": 4.5, + "gateway.fee_percent": 0.029, + }, }, async () => { // Payment gateway integration @@ -255,6 +250,7 @@ Maintain consistent naming patterns following the format `category.metric_name` #### Data Type Selection Choose appropriate data types for your metrics: + - Numeric values for measurements (`response_time_ms`: 250) - Boolean values for state indicators (`cache.hit`: true) - String values for categorical data (`user.subscription`: 'premium') @@ -263,6 +259,7 @@ Choose appropriate data types for your metrics: #### Performance Considerations Be mindful of the performance impact of collecting metrics, especially for high-volume operations: + - Consider implementing sampling for high-frequency operations - Prioritize metrics with the highest analytical value - Avoid redundant or closely correlated metrics @@ -276,6 +273,7 @@ If you're currently using Sentry's standalone Metrics product, migrating to span - **Improved correlation**: Direct association between attributes, traces, and errors Migration process: + 1. Identify your current metric instrumentation points 2. Locate corresponding spans in your application 3. Transfer your metrics to span attributes diff --git a/docs/platforms/javascript/common/configuration/filtering.mdx b/docs/platforms/javascript/common/configuration/filtering.mdx index 8d3fa2d9b9428..deee544c3d503 100644 --- a/docs/platforms/javascript/common/configuration/filtering.mdx +++ b/docs/platforms/javascript/common/configuration/filtering.mdx @@ -176,9 +176,8 @@ Learn more about configuring the sam ## Filtering Spans -Available since JavaScript SDK version `8.2.0`. - Use the configuration option which allows you to provide a function to modify a span. This function is called for the root span and all child spans. If you want to drop the root span, including its child spans, use [`beforeSendTransaction`](#using-beforesendtransaction) instead. +Please note that you cannot use `beforeSendSpan` to drop a span, you can only modify it and filter data on it. diff --git a/docs/platforms/javascript/common/tracing/span-metrics/examples.mdx b/docs/platforms/javascript/common/tracing/span-metrics/examples.mdx index ddbc956dcaf38..7258711cddc32 100644 --- a/docs/platforms/javascript/common/tracing/span-metrics/examples.mdx +++ b/docs/platforms/javascript/common/tracing/span-metrics/examples.mdx @@ -19,86 +19,79 @@ This guide provides practical examples of using span attributes and metrics to s **Solution:** Track the entire file processing pipeline with detailed metrics at each stage, from client-side upload preparation through server-side processing. **Frontend Instrumentation:** + ```javascript // Client-side file upload handling Sentry.startSpan( { - name: 'Client File Upload', - op: 'file.upload.client', + name: "Client File Upload", + op: "file.upload.client", attributes: { // Static details available at the start - 'file.size_bytes': 15728640, // 15MB - 'file.type': 'image/jpeg', - 'file.name': 'user-profile.jpg', - 'client.compression_applied': true - } + "file.size_bytes": 15728640, // 15MB + "file.type": "image/jpeg", + "file.name": "user-profile.jpg", + "client.compression_applied": true, + }, }, - async () => { - // Get the current active span to update during upload - const span = Sentry.getActiveSpan(); - + async (span) => { try { // Begin upload process const uploader = new FileUploader(file); - + // Update progress as upload proceeds - uploader.on('progress', (progressEvent) => { - if (span) { - span.setAttribute('upload.percent_complete', progressEvent.percent); - span.setAttribute('upload.bytes_transferred', progressEvent.loaded); - } + uploader.on("progress", (progressEvent) => { + span.setAttribute("upload.percent_complete", progressEvent.percent); + span.setAttribute("upload.bytes_transferred", progressEvent.loaded); }); - - uploader.on('retry', (retryCount) => { - if (span) { - span.setAttribute('upload.retry_count', retryCount); - } + + uploader.on("retry", (retryCount) => { + span.setAttribute("upload.retry_count", retryCount); }); - + const result = await uploader.start(); - + // Set final attributes after completion - if (span) { - span.setAttribute('upload.total_time_ms', result.totalTime); - span.setAttribute('upload.success', true); - span.setAttribute('upload.server_file_id', result.fileId); - } - + span.setAttribute("upload.total_time_ms", result.totalTime); + span.setAttribute("upload.success", true); + span.setAttribute("upload.server_file_id", result.fileId); + return result; } catch (error) { // Record failure information - if (span) { - span.setAttribute('upload.success', false); - span.setAttribute('upload.error_type', error.name); - span.setAttribute('upload.error_message', error.message); - span.setStatus({ code: 'ERROR' }); - } - throw error; + span.setAttribute("upload.success", false); + Sentry.captureException(error); } } ); ``` **Backend Instrumentation:** + ```javascript // Server-side processing Sentry.startSpan( { - name: 'Server File Processing', - op: 'file.process.server', + name: "Server File Processing", + op: "file.process.server", attributes: { // Server processing steps - 'processing.steps_completed': ['virus_scan', 'resize', 'compress', 'metadata'], - + "processing.steps_completed": [ + "virus_scan", + "resize", + "compress", + "metadata", + ], + // Storage operations - 'storage.provider': 's3', - 'storage.region': 'us-west-2', - 'storage.upload_time_ms': 850, - + "storage.provider": "s3", + "storage.region": "us-west-2", + "storage.upload_time_ms": 850, + // CDN configuration - 'cdn.provider': 'cloudfront', - 'cdn.propagation_ms': 1500 - } + "cdn.provider": "cloudfront", + "cdn.propagation_ms": 1500, + }, }, async () => { // Server-side processing implementation @@ -121,122 +114,111 @@ The frontend span initiates the trace and handles the file upload process. It pr **Solution:** Tracking of the entire LLM interaction flow, from user input to response rendering. **Frontend Instrumentation:** + ```javascript // Client-side LLM interaction handling Sentry.startSpan( { - name: 'LLM Client Interaction', - op: 'ai.client', + name: "LLM Client Interaction", + op: "ai.client", attributes: { // Initial metrics available at request time - 'input.char_count': 280, - 'input.language': 'en', - 'input.type': 'question' - } + "input.char_count": 280, + "input.language": "en", + "input.type": "question", + }, }, - async () => { - const span = Sentry.getActiveSpan(); + async (span) => { const startTime = performance.now(); - + // Begin streaming response from LLM API const stream = await llmClient.createCompletion({ prompt: userInput, - stream: true + stream: true, }); - + // Record time to first token when received let firstTokenReceived = false; let tokensReceived = 0; - + for await (const chunk of stream) { tokensReceived++; - + // Record time to first token if (!firstTokenReceived && chunk.content) { firstTokenReceived = true; const timeToFirstToken = performance.now() - startTime; - - if (span) { - span.setAttribute('ui.time_to_first_token_ms', timeToFirstToken); - } + + span.setAttribute("ui.time_to_first_token_ms", timeToFirstToken); } - + // Process and render the chunk renderChunkToUI(chunk); } - + // Record final metrics after stream completes const totalRequestTime = performance.now() - startTime; - - if (span) { - span.setAttribute('ui.total_request_time_ms', totalRequestTime); - span.setAttribute('stream.rendering_mode', 'markdown'); - span.setAttribute('stream.tokens_received', tokensReceived); - } + + span.setAttribute("ui.total_request_time_ms", totalRequestTime); + span.setAttribute("stream.rendering_mode", "markdown"); + span.setAttribute("stream.tokens_received", tokensReceived); } ); ``` **Backend Instrumentation:** + ```javascript // Server-side LLM processing Sentry.startSpan( { - name: 'LLM API Processing', - op: 'ai.server', + name: "LLM API Processing", + op: "ai.server", attributes: { // Model configuration - known at start - 'llm.model': 'claude-3-5-sonnet-20241022', - 'llm.temperature': 0.5, - 'llm.max_tokens': 4096 - } + "llm.model": "claude-3-5-sonnet-20241022", + "llm.temperature": 0.5, + "llm.max_tokens": 4096, + }, }, - async () => { - const span = Sentry.getActiveSpan(); + async (span) => { const startTime = Date.now(); - + try { // Check rate limits before processing const rateLimits = await getRateLimits(); - if (span) { - span.setAttribute('llm.rate_limit_remaining', rateLimits.remaining); - } - + span.setAttribute("llm.rate_limit_remaining", rateLimits.remaining); + // Make the actual API call to the LLM provider const response = await llmProvider.generateCompletion({ - model: 'claude-3-5-sonnet-20241022', + model: "claude-3-5-sonnet-20241022", prompt: preparedPrompt, temperature: 0.5, - max_tokens: 4096 + max_tokens: 4096, }); - + // Record token usage and performance metrics - if (span) { - span.setAttribute('llm.prompt_tokens', response.usage.prompt_tokens); - span.setAttribute('llm.completion_tokens', response.usage.completion_tokens); - span.setAttribute('llm.total_tokens', response.usage.total_tokens); - span.setAttribute('llm.api_latency_ms', Date.now() - startTime); - - // Calculate and record cost based on token usage - const cost = calculateCost( - response.usage.prompt_tokens, - response.usage.completion_tokens, - 'claude-3-5-sonnet-20241022' - ); - span.setAttribute('llm.cost_usd', cost); - } - + span.setAttribute("llm.prompt_tokens", response.usage.prompt_tokens); + span.setAttribute( + "llm.completion_tokens", + response.usage.completion_tokens + ); + span.setAttribute("llm.total_tokens", response.usage.total_tokens); + span.setAttribute("llm.api_latency_ms", Date.now() - startTime); + + // Calculate and record cost based on token usage + const cost = calculateCost( + response.usage.prompt_tokens, + response.usage.completion_tokens, + "claude-3-5-sonnet-20241022" + ); + span.setAttribute("llm.cost_usd", cost); + return response; } catch (error) { // Record error details - if (span) { - span.setAttribute('error', true); - span.setAttribute('error.type', error.name); - span.setAttribute('error.message', error.message); - span.setAttribute('error.is_rate_limit', error.code === 'rate_limit_exceeded'); - span.setStatus({ code: 'ERROR' }); - } - throw error; + span.setAttribute("error", true); + Sentry.captureException(error); } } ); @@ -258,28 +240,29 @@ The frontend span captures the user interaction and UI rendering performance, wh **Solution:** Track the full checkout process from cart interaction to order fulfillment. **Frontend Instrumentation:** + ```javascript // Client-side checkout process Sentry.startSpan( { - name: 'Checkout UI Flow', - op: 'commerce.checkout.client', + name: "Checkout UI Flow", + op: "commerce.checkout.client", attributes: { // Cart interaction metrics - 'cart.items_added': 3, - 'cart.items_removed': 0, - 'cart.update_count': 2, - + "cart.items_added": 3, + "cart.items_removed": 0, + "cart.update_count": 2, + // User interaction tracking - 'ui.form_completion_time_ms': 45000, - 'ui.payment_method_changes': 1, - 'ui.address_validation_retries': 0, - + "ui.form_completion_time_ms": 45000, + "ui.payment_method_changes": 1, + "ui.address_validation_retries": 0, + // Client performance - 'client.page_load_time_ms': 850, - 'client.payment_widget_load_ms': 650, - 'client.total_interaction_time_ms': 120000 - } + "client.page_load_time_ms": 850, + "client.payment_widget_load_ms": 650, + "client.total_interaction_time_ms": 120000, + }, }, async () => { // Client-side checkout implementation @@ -288,33 +271,34 @@ Sentry.startSpan( ``` **Backend Instrumentation:** + ```javascript // Server-side order processing Sentry.startSpan( { - name: 'Order Processing', - op: 'commerce.order.server', + name: "Order Processing", + op: "commerce.order.server", attributes: { // Order details - 'order.id': 'ord_123456789', - 'order.total_amount': 159.99, - 'order.currency': 'USD', - 'order.items': ['SKU123', 'SKU456', 'SKU789'], - + "order.id": "ord_123456789", + "order.total_amount": 159.99, + "order.currency": "USD", + "order.items": ["SKU123", "SKU456", "SKU789"], + // Payment processing - 'payment.provider': 'stripe', - 'payment.method': 'credit_card', - 'payment.processing_time_ms': 1200, - + "payment.provider": "stripe", + "payment.method": "credit_card", + "payment.processing_time_ms": 1200, + // Inventory checks - 'inventory.check_time_ms': 150, - 'inventory.all_available': true, - + "inventory.check_time_ms": 150, + "inventory.all_available": true, + // Fulfillment - 'fulfillment.warehouse': 'WEST-01', - 'fulfillment.shipping_method': 'express', - 'fulfillment.estimated_delivery': '2024-03-20' - } + "fulfillment.warehouse": "WEST-01", + "fulfillment.shipping_method": "express", + "fulfillment.estimated_delivery": "2024-03-20", + }, }, async () => { // Server-side order processing diff --git a/docs/platforms/javascript/common/tracing/span-metrics/index.mdx b/docs/platforms/javascript/common/tracing/span-metrics/index.mdx index f877c710eaaef..24073f2e51b77 100644 --- a/docs/platforms/javascript/common/tracing/span-metrics/index.mdx +++ b/docs/platforms/javascript/common/tracing/span-metrics/index.mdx @@ -23,14 +23,14 @@ You can enhance existing spans with custom metrics by adding attributes. This is const span = Sentry.getActiveSpan(); if (span) { // Add individual metrics - span.setAttribute('database.rows_affected', 42); - span.setAttribute('cache.hit_rate', 0.85); - + span.setAttribute("database.rows_affected", 42); + span.setAttribute("cache.hit_rate", 0.85); + // Add multiple metrics at once span.setAttributes({ - 'memory.heap_used': 1024000, - 'queue.length': 15, - 'processing.duration_ms': 127 + "memory.heap_used": 1024000, + "queue.length": 15, + "processing.duration_ms": 127, }); } ``` @@ -50,15 +50,15 @@ For more detailed operations, tasks, or process tracking, you can create custom ```javascript Sentry.startSpan( { - name: 'Database Query Metrics', - op: 'db.metrics', + name: "Database Query Metrics", + op: "db.metrics", attributes: { - 'db.query_type': 'SELECT', - 'db.table': 'users', - 'db.execution_time_ms': 45, - 'db.rows_returned': 100, - 'db.connection_pool_size': 5 - } + "db.query_type": "SELECT", + "db.table": "users", + "db.execution_time_ms": 45, + "db.rows_returned": 100, + "db.connection_pool_size": 5, + }, }, () => { // Your database operation here @@ -74,42 +74,35 @@ To consistently add metrics across all spans in your application, you can use th ```javascript Sentry.init({ - beforeSendTransaction(event) { - // Add metrics to the root span - event.contexts.trace.data = { - ...event.contexts.trace.data, - 'app.version': '1.2.3', - 'environment.region': 'us-west-2' + beforeSendSpan(span) { + span.data = { + ...span.data, + "app.version": "1.2.3", + "environment.region": "us-west-2", }; - // Add metrics to all child spans - event.spans.forEach(span => { - span.data = { - ...span.data, - 'app.component_version': '2.0.0', - 'app.deployment_stage': 'production' - }; - }); - - return event; - } + return span; + }, }); ``` ## Best Practices for Span Metrics 1. **Metric Naming** + - Use clear, consistent naming patterns - Include the metric category (examples: `db`, `cache`, `http`) - Use snake_case for metric names 2. **Data Types** + - Use appropriate numeric types for measurements - Use booleans for status flags - Use strings for categorical data - Use arrays when grouping related values 3. **Performance Considerations** + - Consider the overhead of metric collection - Use sampling when collecting high-frequency metrics - Balance metric granularity with system performance @@ -123,11 +116,13 @@ Sentry.init({ When implementing span metrics in your application: 1. **Start Small and Iterate** + - Begin with basic metrics that directly relate to your debugging or performance monitoring needs - Add more detailed tracking as specific debugging needs emerge - Remove metrics that aren't providing actionable insights 2. **Maintain Consistency** + - Use consistent naming patterns across your application - Document metric meanings and units in your codebase - Share common metrics across similar operations @@ -137,4 +132,4 @@ When implementing span metrics in your application: - Consider what alerts or dashboard visualizations you'll want to create - Ensure metrics can drive issue resolution or decision making -For detailed examples of how to implement span metrics in common scenarios, see our Span Metrics Examples guide. \ No newline at end of file +For detailed examples of how to implement span metrics in common scenarios, see our Span Metrics Examples guide.