diff --git a/static/app/components/onboarding/productSelection.tsx b/static/app/components/onboarding/productSelection.tsx index 4693c58a08f7..62e9c0a8bec7 100644 --- a/static/app/components/onboarding/productSelection.tsx +++ b/static/app/components/onboarding/productSelection.tsx @@ -138,6 +138,11 @@ export const platformProductAvailability = { ], 'dotnet-xamarin': [ProductSolution.PERFORMANCE_MONITORING], dart: [ProductSolution.PERFORMANCE_MONITORING, ProductSolution.LOGS], + elixir: [ + ProductSolution.PERFORMANCE_MONITORING, + ProductSolution.LOGS, + ProductSolution.METRICS, + ], kotlin: [ProductSolution.PERFORMANCE_MONITORING], go: [ ProductSolution.PERFORMANCE_MONITORING, @@ -489,16 +494,24 @@ export const platformProductAvailability = { ProductSolution.PERFORMANCE_MONITORING, ProductSolution.PROFILING, ProductSolution.LOGS, + ProductSolution.METRICS, ], 'ruby-rack': [ ProductSolution.PERFORMANCE_MONITORING, ProductSolution.PROFILING, ProductSolution.LOGS, + ProductSolution.METRICS, ], 'ruby-rails': [ ProductSolution.PERFORMANCE_MONITORING, ProductSolution.PROFILING, ProductSolution.LOGS, + ProductSolution.METRICS, + ], + rust: [ + ProductSolution.PERFORMANCE_MONITORING, + ProductSolution.LOGS, + ProductSolution.METRICS, ], unity: [ProductSolution.LOGS, ProductSolution.METRICS], unreal: [ProductSolution.LOGS], diff --git a/static/app/data/platformCategories.tsx b/static/app/data/platformCategories.tsx index e1ff40f52f52..f27e58df312b 100644 --- a/static/app/data/platformCategories.tsx +++ b/static/app/data/platformCategories.tsx @@ -300,7 +300,6 @@ export const withPerformanceOnboarding = new Set([ // List of platforms that do not have performance support. We make use of this list in the product to not provide any Performance // views such as Performance onboarding checklist. export const withoutPerformanceSupport = new Set([ - 'elixir', 'minidump', 'nintendo-switch', 'playstation', @@ -495,6 +494,8 @@ export const withMetricsOnboarding = new Set([ 'ruby', 'ruby-rack', 'ruby-rails', + 'rust', + 'elixir', 'unity', ]); diff --git a/static/app/gettingStartedDocs/elixir/index.tsx b/static/app/gettingStartedDocs/elixir/index.tsx index 4883d00a9755..b1a02a2ebb30 100644 --- a/static/app/gettingStartedDocs/elixir/index.tsx +++ b/static/app/gettingStartedDocs/elixir/index.tsx @@ -1,6 +1,7 @@ import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {crashReport} from 'sentry/gettingStartedDocs/elixir/crashReport'; import {logs} from 'sentry/gettingStartedDocs/elixir/logs'; +import {metrics} from 'sentry/gettingStartedDocs/elixir/metrics'; import {onboarding} from 'sentry/gettingStartedDocs/elixir/onboarding'; import { feedbackOnboardingJsLoader, @@ -13,4 +14,5 @@ export const docs: Docs = { crashReportOnboarding: crashReport, feedbackOnboardingJsLoader, logsOnboarding: logs, + metricsOnboarding: metrics, }; diff --git a/static/app/gettingStartedDocs/elixir/metrics.spec.tsx b/static/app/gettingStartedDocs/elixir/metrics.spec.tsx new file mode 100644 index 000000000000..4db51f5f55fd --- /dev/null +++ b/static/app/gettingStartedDocs/elixir/metrics.spec.tsx @@ -0,0 +1,28 @@ +const {metrics: elixirMetrics} = jest.requireActual( + 'sentry/gettingStartedDocs/elixir/metrics' +); + +describe('metrics', () => { + const mockParams = { + dsn: { + public: 'https://test@example.com/123', + }, + }; + + it('generates metrics onboarding config', () => { + const installSteps = elixirMetrics.install(); + expect(installSteps).toHaveLength(1); + expect(installSteps[0].type).toBe('install'); + + const verifySteps = elixirMetrics.verify(mockParams); + expect(verifySteps).toHaveLength(1); + expect(verifySteps[0].type).toBe('verify'); + + const codeSnippet = verifySteps[0].content[1].code; + expect(codeSnippet).toContain('Sentry.init'); + expect(codeSnippet).toContain(mockParams.dsn.public); + expect(codeSnippet).toContain('Sentry.Metrics.count'); + expect(codeSnippet).toContain('Sentry.Metrics.gauge'); + expect(codeSnippet).toContain('Sentry.Metrics.distribution'); + }); +}); diff --git a/static/app/gettingStartedDocs/elixir/metrics.tsx b/static/app/gettingStartedDocs/elixir/metrics.tsx new file mode 100644 index 000000000000..b6c8952e276e --- /dev/null +++ b/static/app/gettingStartedDocs/elixir/metrics.tsx @@ -0,0 +1,106 @@ +import {ExternalLink} from '@sentry/scraps/link'; + +import type { + DocsParams, + OnboardingConfig, +} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {tct} from 'sentry/locale'; + +const getInstallSnippet = () => ` +defp deps do + [ + # ... + {:sentry, "~> 13.0"}, + {:jason, "~> 1.2"}, + {:hackney, "~> 1.8"} + ] +end`; + +const getVerifySnippet = (params: DocsParams) => ` +Sentry.init( + dsn: "${params.dsn.public}", + environment_name: Mix.env() +) + +# Counter metric +Sentry.Metrics.count( + "button_click", + 5, + attributes: %{browser: "Firefox", app_version: "1.0.0"} +) + +# Gauge metric +Sentry.Metrics.gauge( + "page_load", + 15.0, + unit: "millisecond", + attributes: %{page: "/home"} +) + +# Distribution metric +Sentry.Metrics.distribution( + "page_load", + 15.0, + unit: "millisecond", + attributes: %{page: "/home"} +)`; + +export const metrics: OnboardingConfig = { + install: () => [ + { + type: StepType.INSTALL, + content: [ + { + type: 'text', + text: tct( + 'Metrics are supported in Sentry Elixir SDK version [code:13.0.0] and above. Make sure your [code:mix.exs] specifies at least this version:', + {code: } + ), + }, + { + type: 'code', + language: 'elixir', + code: getInstallSnippet(), + }, + { + type: 'text', + text: tct('Then fetch the updated dependency: [code:mix deps.get]', { + code: , + }), + }, + ], + }, + ], + configure: () => [], + verify: params => [ + { + type: StepType.VERIFY, + content: [ + { + type: 'text', + text: tct( + 'Metrics are automatically enabled. You can emit metrics using the [code:Sentry.Metrics] module.', + {code: } + ), + }, + { + type: 'code', + language: 'elixir', + code: getVerifySnippet(params), + }, + { + type: 'text', + text: tct( + 'For more detailed information, see the [link:metrics documentation].', + { + link: ( + + ), + } + ), + }, + ], + }, + ], +}; diff --git a/static/app/gettingStartedDocs/elixir/onboarding.tsx b/static/app/gettingStartedDocs/elixir/onboarding.tsx index 41f6ad7b824e..1074c7ff902c 100644 --- a/static/app/gettingStartedDocs/elixir/onboarding.tsx +++ b/static/app/gettingStartedDocs/elixir/onboarding.tsx @@ -20,7 +20,20 @@ const getConfigureSnippet = (params: DocsParams) => ` dsn: "${params.dsn.public}", environment_name: Mix.env(), enable_source_code_context: true, - root_source_code_paths: [File.cwd!()]`; + root_source_code_paths: [File.cwd!()]${ + params.isPerformanceSelected + ? `, + # Set traces_sample_rate to 1.0 to capture 100% + # of transactions for tracing. + traces_sample_rate: 1.0` + : '' + }${ + params.isLogsSelected + ? `, + # Enable sending logs to Sentry + enable_logs: true` + : '' + }`; const getPlugSnippet = () => ` defmodule MyAppWeb.Endpoint diff --git a/static/app/gettingStartedDocs/ruby/metrics.spec.tsx b/static/app/gettingStartedDocs/ruby/metrics.spec.tsx new file mode 100644 index 000000000000..632cb02b96f7 --- /dev/null +++ b/static/app/gettingStartedDocs/ruby/metrics.spec.tsx @@ -0,0 +1,30 @@ +const {metrics: rubyMetrics} = jest.requireActual( + 'sentry/gettingStartedDocs/ruby/metrics' +); + +describe('metrics', () => { + const mockParams = { + dsn: { + public: 'https://test@example.com/123', + }, + }; + + it('generates metrics onboarding config', () => { + const config = rubyMetrics({docsPlatform: 'ruby'}); + + const installSteps = config.install(); + expect(installSteps).toHaveLength(1); + expect(installSteps[0].type).toBe('install'); + + const verifySteps = config.verify(mockParams); + expect(verifySteps).toHaveLength(1); + expect(verifySteps[0].type).toBe('verify'); + + const codeSnippet = verifySteps[0].content[1].code; + expect(codeSnippet).toContain('Sentry.init'); + expect(codeSnippet).toContain(mockParams.dsn.public); + expect(codeSnippet).toContain('Sentry.metrics.count'); + expect(codeSnippet).toContain('Sentry.metrics.gauge'); + expect(codeSnippet).toContain('Sentry.metrics.distribution'); + }); +}); diff --git a/static/app/gettingStartedDocs/ruby/metrics.tsx b/static/app/gettingStartedDocs/ruby/metrics.tsx index a47e1eaf1c21..5f13781e2e21 100644 --- a/static/app/gettingStartedDocs/ruby/metrics.tsx +++ b/static/app/gettingStartedDocs/ruby/metrics.tsx @@ -66,13 +66,30 @@ export const metrics = < end # Counter metric -Sentry::Metrics.count('test-counter', value: 10, attributes: { my_attribute: 'foo'}) +Sentry.metrics.count('button_click', value: 5, attributes: { browser: 'Firefox', app_version: '1.0.0' }) # Gauge metric -Sentry::Metrics.gauge('test-gauge', 50.0, unit: 'millisecond', attributes: { my_attribute: 'foo' }) +Sentry.metrics.gauge('page_load', 15.0, unit: 'millisecond', attributes: { page: '/home' }) # Distribution metric -Sentry::Metrics.distribution('test-distribution', 20.0, unit: 'kilobyte', attributes: { my_attribute: 'foo' })`, +Sentry.metrics.distribution('page_load', 15.0, unit: 'millisecond', attributes: { page: '/home' })`, + }, + { + type: 'text', + text: tct( + 'For more detailed information, see the [link:metrics documentation].', + { + link: ( + + ), + } + ), }, ], }, diff --git a/static/app/gettingStartedDocs/rust/index.tsx b/static/app/gettingStartedDocs/rust/index.tsx index 2430ee5546b8..1d2cbb97a757 100644 --- a/static/app/gettingStartedDocs/rust/index.tsx +++ b/static/app/gettingStartedDocs/rust/index.tsx @@ -2,10 +2,12 @@ import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {crashReport} from './crashReport'; import {logs} from './logs'; +import {metrics} from './metrics'; import {onboarding} from './onboarding'; export const docs: Docs = { onboarding, crashReportOnboarding: crashReport, logsOnboarding: logs, + metricsOnboarding: metrics, }; diff --git a/static/app/gettingStartedDocs/rust/metrics.spec.tsx b/static/app/gettingStartedDocs/rust/metrics.spec.tsx new file mode 100644 index 000000000000..0c45920aaf7e --- /dev/null +++ b/static/app/gettingStartedDocs/rust/metrics.spec.tsx @@ -0,0 +1,28 @@ +const {metrics: rustMetrics} = jest.requireActual( + 'sentry/gettingStartedDocs/rust/metrics' +); + +describe('metrics', () => { + const mockParams = { + dsn: { + public: 'https://test@example.com/123', + }, + }; + + it('generates metrics onboarding config', () => { + const installSteps = rustMetrics.install(); + expect(installSteps).toHaveLength(1); + expect(installSteps[0].type).toBe('install'); + + const verifySteps = rustMetrics.verify(mockParams); + expect(verifySteps).toHaveLength(1); + expect(verifySteps[0].type).toBe('verify'); + + const codeSnippet = verifySteps[0].content[1].code; + expect(codeSnippet).toContain('sentry::init'); + expect(codeSnippet).toContain(mockParams.dsn.public); + expect(codeSnippet).toContain('metrics::counter'); + expect(codeSnippet).toContain('metrics::gauge'); + expect(codeSnippet).toContain('metrics::distribution'); + }); +}); diff --git a/static/app/gettingStartedDocs/rust/metrics.tsx b/static/app/gettingStartedDocs/rust/metrics.tsx new file mode 100644 index 000000000000..f91d5cdc05a8 --- /dev/null +++ b/static/app/gettingStartedDocs/rust/metrics.tsx @@ -0,0 +1,76 @@ +import {ExternalLink} from '@sentry/scraps/link'; + +import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {tct} from 'sentry/locale'; + +export const metrics: OnboardingConfig = { + install: () => [ + { + type: StepType.INSTALL, + content: [ + { + type: 'text', + text: tct( + 'Metrics are supported in Sentry Rust SDK version [code:0.48.2] and above. The [code:metrics] feature flag needs to be enabled in your [code:Cargo.toml]:', + {code: } + ), + }, + { + type: 'code', + language: 'toml', + code: `[dependencies] +sentry = { version = "0.48.2", features = ["metrics"] }`, + }, + ], + }, + ], + configure: () => [], + verify: params => [ + { + type: StepType.VERIFY, + content: [ + { + type: 'text', + text: tct( + 'Metrics are enabled by default when the [code:metrics] feature is included. You can emit metrics using the [code:sentry::metrics] API.', + {code: } + ), + }, + { + type: 'code', + language: 'rust', + code: `let _guard = sentry::init(("${params.dsn.public}", sentry::ClientOptions { + release: sentry::release_name!(), + ..Default::default() +})); + +use sentry::metrics; + +// Counter metric +metrics::counter("button_click", 1).capture(); + +// Gauge metric +metrics::gauge("queue.depth", 42).capture(); + +// Distribution metric +metrics::distribution("page_load", 15.5) + .unit(sentry::protocol::Unit::Millisecond) + .attribute("page", "/home") + .capture();`, + }, + { + type: 'text', + text: tct( + 'For more detailed information, see the [link:metrics documentation].', + { + link: ( + + ), + } + ), + }, + ], + }, + ], +}; diff --git a/static/app/gettingStartedDocs/rust/onboarding.tsx b/static/app/gettingStartedDocs/rust/onboarding.tsx index f21ebf30e8b9..6ac74ca6f7ea 100644 --- a/static/app/gettingStartedDocs/rust/onboarding.tsx +++ b/static/app/gettingStartedDocs/rust/onboarding.tsx @@ -24,7 +24,20 @@ let _guard = sentry::init(("${params.dsn.public}", sentry::ClientOptions { release: sentry::release_name!(), // Capture user IPs and potentially sensitive headers when using HTTP server integrations // see https://docs.sentry.io/platforms/rust/data-management/data-collected for more info - send_default_pii: true, + send_default_pii: true,${ + params.isPerformanceSelected + ? ` + // Set traces_sample_rate to 1.0 to capture 100% + // of transactions for tracing. + traces_sample_rate: 1.0,` + : '' + }${ + params.isLogsSelected + ? ` + // Enable sending logs to Sentry + enable_logs: true,` + : '' + } ..Default::default() }));`; @@ -34,7 +47,20 @@ fn main() { release: sentry::release_name!(), // Capture user IPs and potentially sensitive headers when using HTTP server integrations // see https://docs.sentry.io/platforms/rust/data-management/data-collected for more info - send_default_pii: true, + send_default_pii: true,${ + params.isPerformanceSelected + ? ` + // Set traces_sample_rate to 1.0 to capture 100% + // of transactions for tracing. + traces_sample_rate: 1.0,` + : '' + }${ + params.isLogsSelected + ? ` + // Enable sending logs to Sentry + enable_logs: true,` + : '' + } ..Default::default() }));